diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EmptyPathServiceEndpoints.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EmptyPathServiceEndpoints.java index 7d339c5d2..cd3b429bd 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/EmptyPathServiceEndpoints.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EmptyPathServiceEndpoints.java @@ -1,58 +1,78 @@ package com.palantir.product; +import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import java.io.IOException; +import java.util.List; import javax.annotation.Generated; @Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") -public final class EmptyPathServiceEndpoints implements Service { +public final class EmptyPathServiceEndpoints implements UndertowService { private final UndertowEmptyPathService delegate; private EmptyPathServiceEndpoints(UndertowEmptyPathService delegate) { this.delegate = delegate; } - public static Service of(UndertowEmptyPathService delegate) { + public static UndertowService of(UndertowEmptyPathService delegate) { return new EmptyPathServiceEndpoints(delegate); } @Override - public Registrable create(ServiceContext context) { - return new EmptyPathServiceRegistrable(context, delegate); + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of(new EmptyPathEndpoint(runtime, delegate)); } - private static final class EmptyPathServiceRegistrable implements Registrable { + private static final class EmptyPathEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + private final UndertowEmptyPathService delegate; - private final SerializerRegistry serializers; + private final Serializer serializer; + + EmptyPathEndpoint(UndertowRuntime runtime, UndertowEmptyPathService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + boolean result = delegate.emptyPath(); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } - private EmptyPathServiceRegistrable( - ServiceContext context, UndertowEmptyPathService delegate) { - this.serializers = context.serializerRegistry(); - this.delegate = - context.serviceInstrumenter() - .instrument(delegate, UndertowEmptyPathService.class); + @Override + public String template() { + return "/"; } @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry.add( - Endpoint.get("/", "EmptyPathService", "emptyPath"), new EmptyPathHandler()); + public String serviceName() { + return "UndertowEmptyPathService"; } - private class EmptyPathHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - boolean result = delegate.emptyPath(); - serializers.serialize(result, exchange); - } + @Override + public String name() { + return "emptyPath"; + } + + @Override + public HttpHandler handler() { + return this; } } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EteBinaryServiceEndpoints.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EteBinaryServiceEndpoints.java index 598c695b1..86c7ba346 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/EteBinaryServiceEndpoints.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EteBinaryServiceEndpoints.java @@ -1,127 +1,226 @@ package com.palantir.product; -import com.google.common.reflect.TypeToken; +import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.undertow.lib.BinaryResponseBody; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.internal.Auth; -import com.palantir.conjure.java.undertow.lib.internal.BinarySerializers; -import com.palantir.conjure.java.undertow.lib.internal.StringDeserializers; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import com.palantir.tokens.auth.AuthHeader; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import java.io.IOException; import java.io.InputStream; import java.util.Deque; +import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Generated; @Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") -public final class EteBinaryServiceEndpoints implements Service { +public final class EteBinaryServiceEndpoints implements UndertowService { private final UndertowEteBinaryService delegate; private EteBinaryServiceEndpoints(UndertowEteBinaryService delegate) { this.delegate = delegate; } - public static Service of(UndertowEteBinaryService delegate) { + public static UndertowService of(UndertowEteBinaryService delegate) { return new EteBinaryServiceEndpoints(delegate); } @Override - public Registrable create(ServiceContext context) { - return new EteBinaryServiceRegistrable(context, delegate); + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of( + new PostBinaryEndpoint(runtime, delegate), + new GetOptionalBinaryPresentEndpoint(runtime, delegate), + new GetOptionalBinaryEmptyEndpoint(runtime, delegate), + new GetBinaryFailureEndpoint(runtime, delegate)); } - private static final class EteBinaryServiceRegistrable implements Registrable { + private static final class PostBinaryEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + private final UndertowEteBinaryService delegate; - private final SerializerRegistry serializers; - - private EteBinaryServiceRegistrable( - ServiceContext context, UndertowEteBinaryService delegate) { - this.serializers = context.serializerRegistry(); - this.delegate = - context.serviceInstrumenter() - .instrument(delegate, UndertowEteBinaryService.class); - } - - @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry - .add( - Endpoint.post("/binary", "EteBinaryService", "postBinary"), - new PostBinaryHandler()) - .add( - Endpoint.get( - "/binary/optional/present", - "EteBinaryService", - "getOptionalBinaryPresent"), - new GetOptionalBinaryPresentHandler()) - .add( - Endpoint.get( - "/binary/optional/empty", - "EteBinaryService", - "getOptionalBinaryEmpty"), - new GetOptionalBinaryEmptyHandler()) - .add( - Endpoint.get("/binary/failure", "EteBinaryService", "getBinaryFailure"), - new GetBinaryFailureHandler()); - } - - private class PostBinaryHandler implements HttpHandler { - private final TypeToken bodyType = new TypeToken() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - InputStream body = BinarySerializers.deserializeInputStream(exchange); - BinaryResponseBody result = delegate.postBinary(authHeader, body); - BinarySerializers.serialize(result, exchange); - } + PostBinaryEndpoint(UndertowRuntime runtime, UndertowEteBinaryService delegate) { + this.runtime = runtime; + this.delegate = delegate; } - private class GetOptionalBinaryPresentHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Optional result = delegate.getOptionalBinaryPresent(authHeader); - if (result.isPresent()) { - BinarySerializers.serialize(result.get(), exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + InputStream body = runtime.bodySerDe().deserializeInputStream(exchange); + BinaryResponseBody result = delegate.postBinary(authHeader, body); + runtime.bodySerDe().serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/binary"; + } + + @Override + public String serviceName() { + return "UndertowEteBinaryService"; + } + + @Override + public String name() { + return "postBinary"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetOptionalBinaryPresentEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteBinaryService delegate; + + GetOptionalBinaryPresentEndpoint( + UndertowRuntime runtime, UndertowEteBinaryService delegate) { + this.runtime = runtime; + this.delegate = delegate; } - private class GetOptionalBinaryEmptyHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Optional result = delegate.getOptionalBinaryEmpty(authHeader); - if (result.isPresent()) { - BinarySerializers.serialize(result.get(), exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Optional result = delegate.getOptionalBinaryPresent(authHeader); + if (result.isPresent()) { + runtime.bodySerDe().serialize(result.get(), exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class GetBinaryFailureHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - int numBytes = StringDeserializers.deserializeInteger(queryParams.get("numBytes")); - BinaryResponseBody result = delegate.getBinaryFailure(authHeader, numBytes); - BinarySerializers.serialize(result, exchange); + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/binary/optional/present"; + } + + @Override + public String serviceName() { + return "UndertowEteBinaryService"; + } + + @Override + public String name() { + return "getOptionalBinaryPresent"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetOptionalBinaryEmptyEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteBinaryService delegate; + + GetOptionalBinaryEmptyEndpoint(UndertowRuntime runtime, UndertowEteBinaryService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Optional result = delegate.getOptionalBinaryEmpty(authHeader); + if (result.isPresent()) { + runtime.bodySerDe().serialize(result.get(), exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); } } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/binary/optional/empty"; + } + + @Override + public String serviceName() { + return "UndertowEteBinaryService"; + } + + @Override + public String name() { + return "getOptionalBinaryEmpty"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetBinaryFailureEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteBinaryService delegate; + + GetBinaryFailureEndpoint(UndertowRuntime runtime, UndertowEteBinaryService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + int numBytes = runtime.plainSerDe().deserializeInteger(queryParams.get("numBytes")); + BinaryResponseBody result = delegate.getBinaryFailure(authHeader, numBytes); + runtime.bodySerDe().serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/binary/failure"; + } + + @Override + public String serviceName() { + return "UndertowEteBinaryService"; + } + + @Override + public String name() { + return "getBinaryFailure"; + } + + @Override + public HttpHandler handler() { + return this; + } } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EteServiceEndpoints.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EteServiceEndpoints.java index fb62586b4..ffcde1fb1 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/EteServiceEndpoints.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EteServiceEndpoints.java @@ -1,23 +1,22 @@ package com.palantir.product; -import com.google.common.reflect.TypeToken; +import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.lib.SafeLong; import com.palantir.conjure.java.undertow.lib.BinaryResponseBody; +import com.palantir.conjure.java.undertow.lib.Deserializer; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.internal.Auth; -import com.palantir.conjure.java.undertow.lib.internal.BinarySerializers; -import com.palantir.conjure.java.undertow.lib.internal.StringDeserializers; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import com.palantir.ri.ResourceIdentifier; import com.palantir.tokens.auth.AuthHeader; import com.palantir.tokens.auth.BearerToken; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import io.undertow.util.PathTemplateMatch; import io.undertow.util.StatusCodes; import java.io.IOException; @@ -29,406 +28,1237 @@ import javax.annotation.Generated; @Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") -public final class EteServiceEndpoints implements Service { +public final class EteServiceEndpoints implements UndertowService { private final UndertowEteService delegate; private EteServiceEndpoints(UndertowEteService delegate) { this.delegate = delegate; } - public static Service of(UndertowEteService delegate) { + public static UndertowService of(UndertowEteService delegate) { return new EteServiceEndpoints(delegate); } @Override - public Registrable create(ServiceContext context) { - return new EteServiceRegistrable(context, delegate); + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of( + new StringEndpoint(runtime, delegate), + new IntegerEndpoint(runtime, delegate), + new Double_Endpoint(runtime, delegate), + new Boolean_Endpoint(runtime, delegate), + new SafelongEndpoint(runtime, delegate), + new RidEndpoint(runtime, delegate), + new BearertokenEndpoint(runtime, delegate), + new OptionalStringEndpoint(runtime, delegate), + new OptionalEmptyEndpoint(runtime, delegate), + new DatetimeEndpoint(runtime, delegate), + new BinaryEndpoint(runtime, delegate), + new PathEndpoint(runtime, delegate), + new NotNullBodyEndpoint(runtime, delegate), + new AliasOneEndpoint(runtime, delegate), + new OptionalAliasOneEndpoint(runtime, delegate), + new AliasTwoEndpoint(runtime, delegate), + new NotNullBodyExternalImportEndpoint(runtime, delegate), + new OptionalBodyExternalImportEndpoint(runtime, delegate), + new OptionalQueryExternalImportEndpoint(runtime, delegate), + new NoReturnEndpoint(runtime, delegate), + new EnumQueryEndpoint(runtime, delegate), + new EnumListQueryEndpoint(runtime, delegate), + new OptionalEnumQueryEndpoint(runtime, delegate), + new EnumHeaderEndpoint(runtime, delegate)); } - private static final class EteServiceRegistrable implements Registrable { + private static final class StringEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + StringEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + String result = delegate.string(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/string"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "string"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class IntegerEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + IntegerEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + int result = delegate.integer(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/integer"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "integer"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class Double_Endpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + Double_Endpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + double result = delegate.double_(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/double"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "double_"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class Boolean_Endpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + Boolean_Endpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + boolean result = delegate.boolean_(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/boolean"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "boolean_"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class SafelongEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + SafelongEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + SafeLong result = delegate.safelong(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/safelong"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "safelong"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class RidEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + RidEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + ResourceIdentifier result = delegate.rid(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/rid"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "rid"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class BearertokenEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + BearertokenEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + BearerToken result = delegate.bearertoken(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/bearertoken"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "bearertoken"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class OptionalStringEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer> serializer; + + OptionalStringEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Optional result = delegate.optionalString(authHeader); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/optionalString"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "optionalString"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class OptionalEmptyEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer> serializer; + + OptionalEmptyEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Optional result = delegate.optionalEmpty(authHeader); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/optionalEmpty"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "optionalEmpty"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class DatetimeEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + DatetimeEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + OffsetDateTime result = delegate.datetime(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/datetime"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "datetime"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class BinaryEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + BinaryEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + BinaryResponseBody result = delegate.binary(authHeader); + runtime.bodySerDe().serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/binary"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "binary"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class PathEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + PathEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + String param = runtime.plainSerDe().deserializeString(pathParams.get("param")); + String result = delegate.path(authHeader, param); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/path/{param}"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "path"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class NotNullBodyEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Deserializer deserializer; + + private final Serializer serializer; + + NotNullBodyEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = + runtime.bodySerDe().deserializer(new TypeMarker() {}); + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + StringAliasExample notNullBody = deserializer.deserialize(exchange); + StringAliasExample result = delegate.notNullBody(authHeader, notNullBody); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/base/notNullBody"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "notNullBody"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class AliasOneEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + private final UndertowEteService delegate; - private final SerializerRegistry serializers; - - private EteServiceRegistrable(ServiceContext context, UndertowEteService delegate) { - this.serializers = context.serializerRegistry(); - this.delegate = - context.serviceInstrumenter().instrument(delegate, UndertowEteService.class); - } - - @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry - .add(Endpoint.get("/base/string", "EteService", "string"), new StringHandler()) - .add( - Endpoint.get("/base/integer", "EteService", "integer"), - new IntegerHandler()) - .add( - Endpoint.get("/base/double", "EteService", "double_"), - new Double_Handler()) - .add( - Endpoint.get("/base/boolean", "EteService", "boolean_"), - new Boolean_Handler()) - .add( - Endpoint.get("/base/safelong", "EteService", "safelong"), - new SafelongHandler()) - .add(Endpoint.get("/base/rid", "EteService", "rid"), new RidHandler()) - .add( - Endpoint.get("/base/bearertoken", "EteService", "bearertoken"), - new BearertokenHandler()) - .add( - Endpoint.get("/base/optionalString", "EteService", "optionalString"), - new OptionalStringHandler()) - .add( - Endpoint.get("/base/optionalEmpty", "EteService", "optionalEmpty"), - new OptionalEmptyHandler()) - .add( - Endpoint.get("/base/datetime", "EteService", "datetime"), - new DatetimeHandler()) - .add(Endpoint.get("/base/binary", "EteService", "binary"), new BinaryHandler()) - .add( - Endpoint.get("/base/path/{param}", "EteService", "path"), - new PathHandler()) - .add( - Endpoint.post("/base/notNullBody", "EteService", "notNullBody"), - new NotNullBodyHandler()) - .add( - Endpoint.get("/base/aliasOne", "EteService", "aliasOne"), - new AliasOneHandler()) - .add( - Endpoint.get( - "/base/optionalAliasOne", "EteService", "optionalAliasOne"), - new OptionalAliasOneHandler()) - .add( - Endpoint.get("/base/aliasTwo", "EteService", "aliasTwo"), - new AliasTwoHandler()) - .add( - Endpoint.post( - "/base/external/notNullBody", - "EteService", - "notNullBodyExternalImport"), - new NotNullBodyExternalImportHandler()) - .add( - Endpoint.post( - "/base/external/optional-body", - "EteService", - "optionalBodyExternalImport"), - new OptionalBodyExternalImportHandler()) - .add( - Endpoint.post( - "/base/external/optional-query", - "EteService", - "optionalQueryExternalImport"), - new OptionalQueryExternalImportHandler()) - .add( - Endpoint.post("/base/no-return", "EteService", "noReturn"), - new NoReturnHandler()) - .add( - Endpoint.get("/base/enum/query", "EteService", "enumQuery"), - new EnumQueryHandler()) - .add( - Endpoint.get("/base/enum/list/query", "EteService", "enumListQuery"), - new EnumListQueryHandler()) - .add( - Endpoint.get( - "/base/enum/optional/query", "EteService", "optionalEnumQuery"), - new OptionalEnumQueryHandler()) - .add( - Endpoint.get("/base/enum/header", "EteService", "enumHeader"), - new EnumHeaderHandler()); - } - - private class StringHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - String result = delegate.string(authHeader); - serializers.serialize(result, exchange); - } + private final Serializer serializer; + + AliasOneEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); } - private class IntegerHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - int result = delegate.integer(authHeader); - serializers.serialize(result, exchange); - } + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + String queryParamNameRaw = + runtime.plainSerDe().deserializeString(queryParams.get("queryParamName")); + StringAliasExample queryParamName = StringAliasExample.of(queryParamNameRaw); + StringAliasExample result = delegate.aliasOne(authHeader, queryParamName); + serializer.serialize(result, exchange); } - private class Double_Handler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - double result = delegate.double_(authHeader); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.GET; } - private class Boolean_Handler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - boolean result = delegate.boolean_(authHeader); - serializers.serialize(result, exchange); - } + @Override + public String template() { + return "/base/aliasOne"; } - private class SafelongHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - SafeLong result = delegate.safelong(authHeader); - serializers.serialize(result, exchange); - } + @Override + public String serviceName() { + return "UndertowEteService"; } - private class RidHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - ResourceIdentifier result = delegate.rid(authHeader); - serializers.serialize(result, exchange); - } + @Override + public String name() { + return "aliasOne"; } - private class BearertokenHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - BearerToken result = delegate.bearertoken(authHeader); - serializers.serialize(result, exchange); - } + @Override + public HttpHandler handler() { + return this; } + } - private class OptionalStringHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Optional result = delegate.optionalString(authHeader); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + private static final class OptionalAliasOneEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + OptionalAliasOneEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); } - private class OptionalEmptyHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Optional result = delegate.optionalEmpty(authHeader); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + Optional queryParamNameRaw = + runtime.plainSerDe() + .deserializeOptionalString(queryParams.get("queryParamName")); + Optional queryParamName = + Optional.ofNullable( + queryParamNameRaw.isPresent() + ? StringAliasExample.of(queryParamNameRaw.get()) + : null); + StringAliasExample result = delegate.optionalAliasOne(authHeader, queryParamName); + serializer.serialize(result, exchange); } - private class DatetimeHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - OffsetDateTime result = delegate.datetime(authHeader); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.GET; } - private class BinaryHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - BinaryResponseBody result = delegate.binary(authHeader); - BinarySerializers.serialize(result, exchange); - } + @Override + public String template() { + return "/base/optionalAliasOne"; } - private class PathHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - String param = StringDeserializers.deserializeString(pathParams.get("param")); - String result = delegate.path(authHeader, param); - serializers.serialize(result, exchange); - } + @Override + public String serviceName() { + return "UndertowEteService"; } - private class NotNullBodyHandler implements HttpHandler { - private final TypeToken notNullBodyType = - new TypeToken() {}; + @Override + public String name() { + return "optionalAliasOne"; + } - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - StringAliasExample notNullBody = serializers.deserialize(notNullBodyType, exchange); - StringAliasExample result = delegate.notNullBody(authHeader, notNullBody); - serializers.serialize(result, exchange); - } + @Override + public HttpHandler handler() { + return this; } + } - private class AliasOneHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - String queryParamNameRaw = - StringDeserializers.deserializeString(queryParams.get("queryParamName")); - StringAliasExample queryParamName = StringAliasExample.of(queryParamNameRaw); - StringAliasExample result = delegate.aliasOne(authHeader, queryParamName); - serializers.serialize(result, exchange); - } + private static final class AliasTwoEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + AliasTwoEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); } - private class OptionalAliasOneHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - Optional queryParamNameRaw = - StringDeserializers.deserializeOptionalString( - queryParams.get("queryParamName")); - Optional queryParamName = - Optional.ofNullable( - queryParamNameRaw.isPresent() - ? StringAliasExample.of(queryParamNameRaw.get()) - : null); - StringAliasExample result = delegate.optionalAliasOne(authHeader, queryParamName); - serializers.serialize(result, exchange); - } + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + String queryParamNameRaw = + runtime.plainSerDe().deserializeString(queryParams.get("queryParamName")); + NestedStringAliasExample queryParamName = + NestedStringAliasExample.of(StringAliasExample.of(queryParamNameRaw)); + NestedStringAliasExample result = delegate.aliasTwo(authHeader, queryParamName); + serializer.serialize(result, exchange); } - private class AliasTwoHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - String queryParamNameRaw = - StringDeserializers.deserializeString(queryParams.get("queryParamName")); - NestedStringAliasExample queryParamName = - NestedStringAliasExample.of(StringAliasExample.of(queryParamNameRaw)); - NestedStringAliasExample result = delegate.aliasTwo(authHeader, queryParamName); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.GET; } - private class NotNullBodyExternalImportHandler implements HttpHandler { - private final TypeToken notNullBodyType = - new TypeToken() {}; + @Override + public String template() { + return "/base/aliasTwo"; + } - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - StringAliasExample notNullBody = serializers.deserialize(notNullBodyType, exchange); - StringAliasExample result = - delegate.notNullBodyExternalImport(authHeader, notNullBody); - serializers.serialize(result, exchange); - } + @Override + public String serviceName() { + return "UndertowEteService"; } - private class OptionalBodyExternalImportHandler implements HttpHandler { - private final TypeToken> bodyType = - new TypeToken>() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Optional body = serializers.deserialize(bodyType, exchange); - Optional result = - delegate.optionalBodyExternalImport(authHeader, body); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public String name() { + return "aliasTwo"; } - private class OptionalQueryExternalImportHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - Optional query = - StringDeserializers.deserializeOptionalComplex( - queryParams.get("query"), StringAliasExample::valueOf); - Optional result = - delegate.optionalQueryExternalImport(authHeader, query); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class NotNullBodyExternalImportEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Deserializer deserializer; + + private final Serializer serializer; + + NotNullBodyExternalImportEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = + runtime.bodySerDe().deserializer(new TypeMarker() {}); + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + StringAliasExample notNullBody = deserializer.deserialize(exchange); + StringAliasExample result = delegate.notNullBodyExternalImport(authHeader, notNullBody); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/base/external/notNullBody"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "notNullBodyExternalImport"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class OptionalBodyExternalImportEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Deserializer> deserializer; + + private final Serializer> serializer; + + OptionalBodyExternalImportEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = + runtime.bodySerDe() + .deserializer(new TypeMarker>() {}); + this.serializer = + runtime.bodySerDe() + .serializer(new TypeMarker>() {}); } - private class NoReturnHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - delegate.noReturn(authHeader); + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Optional body = deserializer.deserialize(exchange); + Optional result = + delegate.optionalBodyExternalImport(authHeader, body); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class EnumQueryHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - SimpleEnum queryParamName = - StringDeserializers.deserializeComplex( - queryParams.get("queryParamName"), SimpleEnum::valueOf); - SimpleEnum result = delegate.enumQuery(authHeader, queryParamName); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.POST; } - private class EnumListQueryHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - List queryParamName = - StringDeserializers.deserializeComplexList( - queryParams.get("queryParamName"), SimpleEnum::valueOf); - List result = delegate.enumListQuery(authHeader, queryParamName); - serializers.serialize(result, exchange); - } + @Override + public String template() { + return "/base/external/optional-body"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "optionalBodyExternalImport"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class OptionalQueryExternalImportEndpoint + implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer> serializer; + + OptionalQueryExternalImportEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe() + .serializer(new TypeMarker>() {}); } - private class OptionalEnumQueryHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - Optional queryParamName = - StringDeserializers.deserializeOptionalComplex( - queryParams.get("queryParamName"), SimpleEnum::valueOf); - Optional result = - delegate.optionalEnumQuery(authHeader, queryParamName); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + Optional query = + runtime.plainSerDe() + .deserializeOptionalComplex( + queryParams.get("query"), StringAliasExample::valueOf); + Optional result = + delegate.optionalQueryExternalImport(authHeader, query); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class EnumHeaderHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - HeaderMap headerParams = exchange.getRequestHeaders(); - SimpleEnum headerParameter = - StringDeserializers.deserializeComplex( - headerParams.get("Custom-Header"), SimpleEnum::valueOf); - SimpleEnum result = delegate.enumHeader(authHeader, headerParameter); - serializers.serialize(result, exchange); + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/base/external/optional-query"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "optionalQueryExternalImport"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class NoReturnEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + NoReturnEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + delegate.noReturn(authHeader); + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/base/no-return"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "noReturn"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class EnumQueryEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + EnumQueryEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + SimpleEnum queryParamName = + runtime.plainSerDe() + .deserializeComplex( + queryParams.get("queryParamName"), SimpleEnum::valueOf); + SimpleEnum result = delegate.enumQuery(authHeader, queryParamName); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/enum/query"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "enumQuery"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class EnumListQueryEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer> serializer; + + EnumListQueryEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + List queryParamName = + runtime.plainSerDe() + .deserializeComplexList( + queryParams.get("queryParamName"), SimpleEnum::valueOf); + List result = delegate.enumListQuery(authHeader, queryParamName); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/enum/list/query"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "enumListQuery"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class OptionalEnumQueryEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer> serializer; + + OptionalEnumQueryEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + Optional queryParamName = + runtime.plainSerDe() + .deserializeOptionalComplex( + queryParams.get("queryParamName"), SimpleEnum::valueOf); + Optional result = delegate.optionalEnumQuery(authHeader, queryParamName); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); } } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/enum/optional/query"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "optionalEnumQuery"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class EnumHeaderEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowEteService delegate; + + private final Serializer serializer; + + EnumHeaderEndpoint(UndertowRuntime runtime, UndertowEteService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + HeaderMap headerParams = exchange.getRequestHeaders(); + SimpleEnum headerParameter = + runtime.plainSerDe() + .deserializeComplex( + headerParams.get("Custom-Header"), SimpleEnum::valueOf); + SimpleEnum result = delegate.enumHeader(authHeader, headerParameter); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/enum/header"; + } + + @Override + public String serviceName() { + return "UndertowEteService"; + } + + @Override + public String name() { + return "enumHeader"; + } + + @Override + public HttpHandler handler() { + return this; + } } } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java index 586a49e83..9d313bfd7 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java @@ -16,21 +16,19 @@ package com.palantir.conjure.java.services; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.google.common.reflect.TypeToken; +import com.google.common.collect.MoreCollectors; import com.palantir.conjure.java.ConjureAnnotations; import com.palantir.conjure.java.FeatureFlags; import com.palantir.conjure.java.types.CodeBlocks; import com.palantir.conjure.java.types.TypeMapper; +import com.palantir.conjure.java.undertow.lib.Deserializer; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.internal.Auth; -import com.palantir.conjure.java.undertow.lib.internal.BinarySerializers; -import com.palantir.conjure.java.undertow.lib.internal.StringDeserializers; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import com.palantir.conjure.java.visitor.DefaultTypeVisitor; import com.palantir.conjure.java.visitor.MoreVisitors; import com.palantir.conjure.spec.ArgumentDefinition; @@ -64,6 +62,8 @@ import com.squareup.javapoet.TypeSpec; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import java.io.IOException; import java.io.InputStream; @@ -82,10 +82,10 @@ final class UndertowServiceHandlerGenerator { private static final String EXCHANGE_VAR_NAME = "exchange"; - private static final String SERIALIZER_REGISTRY_VAR_NAME = "serializers"; private static final String DELEGATE_VAR_NAME = "delegate"; - private static final String CONTEXT_VAR_NAME = "context"; - + private static final String RUNTIME_VAR_NAME = "runtime"; + private static final String DESERIALIZER_VAR_NAME = "deserializer"; + private static final String SERIALIZER_VAR_NAME = "serializer"; private static final String AUTH_HEADER_VAR_NAME = "authHeader"; private static final String COOKIE_TOKEN_VAR_NAME = "cookieToken"; @@ -104,88 +104,58 @@ public JavaFile generateServiceHandler(ServiceDefinition serviceDefinition, List ClassName serviceClass = ClassName.get(serviceDefinition.getServiceName().getPackage(), (experimentalFeatures.contains(FeatureFlags.UndertowServicePrefix) ? "Undertow" : "") + serviceDefinition.getServiceName().getName()); - TypeSpec.Builder registrable = TypeSpec.classBuilder(serviceName + "Registrable") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) - .addSuperinterface(Registrable.class); + TypeSpec.Builder factory = TypeSpec.classBuilder(serviceName + "Factory") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL); // addFields - registrable.addField(serviceClass, DELEGATE_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL); - registrable.addField(ClassName.get(SerializerRegistry.class), SERIALIZER_REGISTRY_VAR_NAME, + factory.addField(serviceClass, DELEGATE_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL); + factory.addField(ClassName.get(UndertowRuntime.class), RUNTIME_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL); // addConstructor - registrable.addMethod(MethodSpec.constructorBuilder() + factory.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) - .addParameter(ServiceContext.class, CONTEXT_VAR_NAME) + .addParameter(UndertowRuntime.class, RUNTIME_VAR_NAME) .addParameter(serviceClass, DELEGATE_VAR_NAME) - .addStatement("this.$1N = $2N.serializerRegistry()", SERIALIZER_REGISTRY_VAR_NAME, CONTEXT_VAR_NAME) - .addStatement("this.$1N = $2N.serviceInstrumenter().instrument($1N, $3T.class)", - DELEGATE_VAR_NAME, CONTEXT_VAR_NAME, serviceClass) + .addStatement("this.$1N = $1N", RUNTIME_VAR_NAME) + .addStatement("this.$1N = $1N", DELEGATE_VAR_NAME) .build()); - // implement Registrable#add interface - // TODO(nmiyake): check for path disjointness per https://palantir.quip.com/5VxNAIyYYvnZ. Eventually, this - // should be enforced at the IR level -- once that is done, the generator will not need to perform any - // validation as the proper endpoint uniqueness guarantees will be provided by the IR itself. - CodeBlock routingHandler = CodeBlock.builder() - .add(CodeBlocks.of(Iterables.transform( - serviceDefinition.getEndpoints(), - e -> CodeBlock.of( - ".add($1L, $2L)", - CodeBlock.of( - "$1T.$2L($3S, $4S, $5S)", - Endpoint.class, - e.getHttpMethod().toString().toLowerCase(), - e.getHttpPath(), - serviceName, - e.getEndpointName().get()), - CodeBlock.of( - "new $1T()", - endpointToHandlerType(serviceDefinition.getServiceName(), e.getEndpointName())) - )))) - .build(); - registrable.addMethod(MethodSpec.methodBuilder("register") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .addParameter(EndpointRegistry.class, "endpointRegistry") - .addStatement("$1L$2L", "endpointRegistry", routingHandler) - .build()); - - // addEndpointHandlers - registrable.addTypes(Iterables.transform(serviceDefinition.getEndpoints(), - e -> generateEndpointHandler(e, typeDefinitions, typeMapper, returnTypeMapper))); - - TypeSpec routable = registrable.build(); - ClassName serviceType = ClassName.get(serviceDefinition.getServiceName().getPackage(), serviceDefinition.getServiceName().getName() + "Endpoints"); - ClassName registrableName = ClassName.get(serviceDefinition.getServiceName().getPackage(), - serviceType.simpleName(), serviceName + "Registrable"); + CodeBlock endpointBlock = CodeBlock.builder().build(); + for (EndpointDefinition e : serviceDefinition.getEndpoints()) { + CodeBlock nextBlock = CodeBlock.of("new $1T($2N, $3N)", + endpointToHandlerType(serviceDefinition.getServiceName(), e.getEndpointName()), + RUNTIME_VAR_NAME, DELEGATE_VAR_NAME); + endpointBlock = endpointBlock.isEmpty() ? nextBlock : CodeBlock.of("$1L, $2L", endpointBlock, nextBlock); + } + TypeSpec endpoints = TypeSpec.classBuilder(serviceType.simpleName()) .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(UndertowServiceHandlerGenerator.class)) - .addSuperinterface(Service.class) + .addSuperinterface(UndertowService.class) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addField(FieldSpec.builder(serviceClass, DELEGATE_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL).build()) .addMethod(MethodSpec.methodBuilder("of") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(serviceClass, DELEGATE_VAR_NAME) .addStatement("return new $T($N)", serviceType, DELEGATE_VAR_NAME) - .returns(Service.class) + .returns(UndertowService.class) .build()) .addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) .addParameter(serviceClass, DELEGATE_VAR_NAME) .addStatement("this.$1N = $1N", DELEGATE_VAR_NAME) .build()) - .addMethod(MethodSpec.methodBuilder("create") + .addMethod(MethodSpec.methodBuilder("endpoints") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) - .addParameter(ServiceContext.class, CONTEXT_VAR_NAME) - .returns(Registrable.class) - .addStatement("return new $1T($2N, $3N)", registrableName, CONTEXT_VAR_NAME, DELEGATE_VAR_NAME) + .addParameter(UndertowRuntime.class, RUNTIME_VAR_NAME) + .returns(ParameterizedTypeName.get(List.class, Endpoint.class)) + .addStatement("return $1T.of($2L)", ImmutableList.class, endpointBlock) .build()) - - .addType(routable) + .addTypes(Iterables.transform(serviceDefinition.getEndpoints(), + e -> generateEndpointHandler(e, serviceClass, typeDefinitions, typeMapper, returnTypeMapper))) .build(); return JavaFile.builder(serviceDefinition.getServiceName().getPackage(), endpoints) @@ -195,49 +165,112 @@ public JavaFile generateServiceHandler(ServiceDefinition serviceDefinition, List } private TypeName endpointToHandlerType(com.palantir.conjure.spec.TypeName serviceName, EndpointName name) { - return ClassName.get(serviceName.getPackage(), - serviceName.getName() + "Endpoints", serviceName.getName() + "Registrable", + return ClassName.get(serviceName.getPackage(), serviceName.getName() + "Endpoints", endpointToHandlerClassName(name)); } private String endpointToHandlerClassName(EndpointName name) { - return StringUtils.capitalize(name.get()) + "Handler"; + return StringUtils.capitalize(name.get()) + "Endpoint"; } - private TypeSpec generateEndpointHandler(EndpointDefinition endpointDefinition, + private TypeSpec generateEndpointHandler( + EndpointDefinition endpointDefinition, + ClassName serviceClass, List typeDefinitions, TypeMapper typeMapper, TypeMapper returnTypeMapper) { - MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("handleRequest") + MethodSpec.Builder handleMethodBuilder = MethodSpec.methodBuilder("handleRequest") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(HttpServerExchange.class, EXCHANGE_VAR_NAME) .addException(IOException.class) .addCode(endpointInvocation(endpointDefinition, typeDefinitions, typeMapper, returnTypeMapper)); - endpointDefinition.getDeprecated().ifPresent(deprecatedDocsValue -> methodBuilder.addAnnotation( + endpointDefinition.getDeprecated().ifPresent(deprecatedDocsValue -> handleMethodBuilder.addAnnotation( AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "deprecation") .build())); - return TypeSpec.classBuilder(endpointToHandlerClassName(endpointDefinition.getEndpointName())) - .addModifiers(Modifier.PRIVATE) - .addSuperinterface(HttpHandler.class) - .addFields(endpointDefinition.getArgs().stream() - .filter(def -> def.getParamType().accept(ParameterTypeVisitor.IS_BODY)) - .map(def -> createTypeField(typeMapper, def)) - .collect(Collectors.toList())) - .addMethod(methodBuilder.build()) - .build(); - } + MethodSpec.Builder ctorBuilder = MethodSpec.constructorBuilder() + .addParameter(UndertowRuntime.class, RUNTIME_VAR_NAME) + .addParameter(serviceClass, DELEGATE_VAR_NAME) + .addStatement("this.$1N = $1N", RUNTIME_VAR_NAME) + .addStatement("this.$1N = $1N", DELEGATE_VAR_NAME); + + TypeSpec.Builder endpointBuilder = + TypeSpec.classBuilder(endpointToHandlerClassName(endpointDefinition.getEndpointName())) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addSuperinterface(HttpHandler.class) + .addSuperinterface(Endpoint.class) + .addField(FieldSpec.builder( + UndertowRuntime.class, RUNTIME_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL).build()) + .addField(FieldSpec.builder(serviceClass, DELEGATE_VAR_NAME, Modifier.PRIVATE, + Modifier.FINAL).build()); + + getBodyParamTypeArgument(endpointDefinition.getArgs()) + .map(ArgumentDefinition::getType) + // Filter out binary data + .flatMap(type -> type.accept(TypeVisitor.IS_BINARY) ? Optional.empty() : Optional.of(type)) + .map(typeMapper::getClassName) + .map(TypeName::box) + .ifPresent(typeName -> { + TypeName type = ParameterizedTypeName.get(ClassName.get(Deserializer.class), typeName); + endpointBuilder.addField(FieldSpec.builder( + type, DESERIALIZER_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL).build()); + ctorBuilder.addStatement("this.$1N = $2N.bodySerDe().deserializer(new $3T() {})", + DESERIALIZER_VAR_NAME, RUNTIME_VAR_NAME, + ParameterizedTypeName.get(ClassName.get(TypeMarker.class), typeName)); + }); + + endpointDefinition.getReturns().ifPresent(returnType -> { + if (!UndertowTypeFunctions.isOptionalBinary(returnType) && !returnType.accept(TypeVisitor.IS_BINARY)) { + TypeName typeName = returnTypeMapper.getClassName(returnType).box(); + TypeName type = ParameterizedTypeName.get(ClassName.get(Serializer.class), typeName); + endpointBuilder.addField(FieldSpec.builder(type, + SERIALIZER_VAR_NAME, Modifier.PRIVATE, Modifier.FINAL).build()); + ctorBuilder.addStatement("this.$1N = $2N.bodySerDe().serializer(new $3T() {})", SERIALIZER_VAR_NAME, + RUNTIME_VAR_NAME, ParameterizedTypeName.get(ClassName.get(TypeMarker.class), typeName)); + } + }); - private static FieldSpec createTypeField(TypeMapper typeMapper, ArgumentDefinition argument) { - String name = argument.getArgName().get() + "Type"; - TypeName type = ParameterizedTypeName.get( - ClassName.get(TypeToken.class), typeMapper.getClassName(argument.getType())); - return FieldSpec.builder(type, name, Modifier.PRIVATE, Modifier.FINAL) - .initializer("new $T() {}", type) - .build(); + endpointBuilder + .addMethod(ctorBuilder.build()) + .addMethod(handleMethodBuilder.build()) + .addMethod(MethodSpec.methodBuilder("method") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(HttpString.class) + .addStatement("return $1T.$2N", Methods.class, endpointDefinition.getHttpMethod().toString()) + .build()) + .addMethod(MethodSpec.methodBuilder("template") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addStatement("return $1S", endpointDefinition.getHttpPath()) + .build()) + .addMethod(MethodSpec.methodBuilder("serviceName") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + // Note that this is the service name as defined in conjure, not the potentially modified + // name of the generated service interface. We may generate "UndertowFooService", but we + // should still return "FooService" here. + .addStatement("return $1S", serviceClass.simpleName()) + .build()) + .addMethod(MethodSpec.methodBuilder("name") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addStatement("return $1S", endpointDefinition.getEndpointName().get()) + .build()) + .addMethod(MethodSpec.methodBuilder("handler") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(HttpHandler.class) + .addStatement("return this") + .build()); + + return endpointBuilder.build(); } private static final String PATH_PARAMS_VAR_NAME = "pathParams"; @@ -255,14 +288,13 @@ private CodeBlock endpointInvocation(EndpointDefinition endpointDefinition, List getBodyParamTypeArgument(endpointDefinition.getArgs()).ifPresent(bodyParam -> { if (bodyParam.getType().accept(TypeVisitor.IS_BINARY)) { // TODO(ckozak): Support aliased and optional binary types - code.addStatement("$1T $2N = $3T.deserializeInputStream($4N)", - InputStream.class, bodyParam.getArgName().get(), BinarySerializers.class, EXCHANGE_VAR_NAME); + code.addStatement("$1T $2N = $3N.bodySerDe().deserializeInputStream($4N)", + InputStream.class, bodyParam.getArgName().get(), RUNTIME_VAR_NAME, EXCHANGE_VAR_NAME); } else { - code.addStatement("$1T $2N = $3N.deserialize($4N, $5N)", + code.addStatement("$1T $2N = $3N.deserialize($4N)", typeMapper.getClassName(bodyParam.getType()).box(), bodyParam.getArgName().get(), - SERIALIZER_REGISTRY_VAR_NAME, - bodyParam.getArgName().get() + "Type", + DESERIALIZER_VAR_NAME, EXCHANGE_VAR_NAME); } }); @@ -298,10 +330,10 @@ private CodeBlock endpointInvocation(EndpointDefinition endpointDefinition, List if (UndertowTypeFunctions.toConjureTypeWithoutAliases(returnType, typeDefinitions) .accept(TypeVisitor.IS_OPTIONAL)) { CodeBlock serializer = UndertowTypeFunctions.isOptionalBinary(returnType) - ? CodeBlock.builder().add("$1T.serialize($2N.get(), $3N)", - BinarySerializers.class, resultVarName, EXCHANGE_VAR_NAME).build() + ? CodeBlock.builder().add("$1N.bodySerDe().serialize($2N.get(), $3N)", + RUNTIME_VAR_NAME, resultVarName, EXCHANGE_VAR_NAME).build() : CodeBlock.builder().add("$1N.serialize($2N, $3N)", - SERIALIZER_REGISTRY_VAR_NAME, resultVarName, EXCHANGE_VAR_NAME).build(); + SERIALIZER_VAR_NAME, resultVarName, EXCHANGE_VAR_NAME).build(); // For optional<>: set response code to 204/NO_CONTENT if result is absent code.add( CodeBlock.builder() @@ -314,11 +346,11 @@ private CodeBlock endpointInvocation(EndpointDefinition endpointDefinition, List .build()); } else { if (returnType.accept(TypeVisitor.IS_BINARY)) { - code.addStatement("$1T.serialize($2N, $3N)", - BinarySerializers.class, resultVarName, EXCHANGE_VAR_NAME); + code.addStatement("$1N.bodySerDe().serialize($2N, $3N)", + RUNTIME_VAR_NAME, resultVarName, EXCHANGE_VAR_NAME); } else { code.addStatement("$1N.serialize($2N, $3N)", - SERIALIZER_REGISTRY_VAR_NAME, resultVarName, EXCHANGE_VAR_NAME); + SERIALIZER_VAR_NAME, resultVarName, EXCHANGE_VAR_NAME); } } } else { @@ -347,17 +379,17 @@ private Optional addAuthCode( @Override public Optional visitHeader(HeaderAuthType value) { // header auth - code.addStatement("$1T $2N = $3T.header($4N)", - AuthHeader.class, AUTH_HEADER_VAR_NAME, Auth.class, EXCHANGE_VAR_NAME); + code.addStatement("$1T $2N = $3N.auth().header($4N)", + AuthHeader.class, AUTH_HEADER_VAR_NAME, RUNTIME_VAR_NAME, EXCHANGE_VAR_NAME); return Optional.of(AUTH_HEADER_VAR_NAME); } @Override public Optional visitCookie(CookieAuthType value) { - code.addStatement("$1T $2N = $3T.cookie($4N, $5S)", + code.addStatement("$1T $2N = $3N.auth().cookie($4N, $5S)", BearerToken.class, COOKIE_TOKEN_VAR_NAME, - Auth.class, + RUNTIME_VAR_NAME, EXCHANGE_VAR_NAME, endpointDefinition.getAuth().get().accept(AuthTypeVisitor.COOKIE).getCookieName()); return Optional.of(COOKIE_TOKEN_VAR_NAME); @@ -497,11 +529,11 @@ private CodeBlock decodePlainParameterCodeBlock(Type type, TypeMapper typeMapper String paramsVarName, String paramId) { if (type.accept(MoreVisitors.IS_EXTERNAL)) { return CodeBlocks.statement( - "$1T $2N = $3T.valueOf($4T.deserializeString($5N.get($6S)))", + "$1T $2N = $3T.valueOf($4N.plainSerDe().deserializeString($5N.get($6S)))", typeMapper.getClassName(type), resultVarName, typeMapper.getClassName(type), - StringDeserializers.class, + RUNTIME_VAR_NAME, paramsVarName, paramId ); @@ -512,10 +544,10 @@ private CodeBlock decodePlainParameterCodeBlock(Type type, TypeMapper typeMapper return complexDeserializer.get(); } return CodeBlocks.statement( - "$1T $2N = $3T.$4L($5N.get($6S))", + "$1T $2N = $3N.plainSerDe().$4L($5N.get($6S))", typeMapper.getClassName(type), resultVarName, - ClassName.get(StringDeserializers.class), + RUNTIME_VAR_NAME, deserializeFunctionName(type), paramsVarName, paramId @@ -567,10 +599,10 @@ public Optional visitDefault() { return Optional.empty(); } }).map(functionName -> CodeBlocks.statement( - "$1T $2N = $3T.$4L($5N.get($6S), $7T::valueOf)", + "$1T $2N = $3N.plainSerDe().$4L($5N.get($6S), $7T::valueOf)", typeMapper.getClassName(type), resultVarName, - StringDeserializers.class, + RUNTIME_VAR_NAME, functionName, paramsVarName, paramId, @@ -709,11 +741,8 @@ private static String deserializeFunctionName(Type type) { } private Optional getBodyParamTypeArgument(List args) { - List bodyArgs = args.stream().filter( - arg -> arg.getParamType().accept(ParameterTypeVisitor.IS_BODY)).collect(Collectors.toList()); - if (bodyArgs.isEmpty()) { - return Optional.empty(); - } - return Optional.of(Iterables.getOnlyElement(bodyArgs)); + return args.stream() + .filter(arg -> arg.getParamType().accept(ParameterTypeVisitor.IS_BODY)) + .collect(MoreCollectors.toOptional()); } } diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java index 3ee650fba..c585007e2 100644 --- a/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java +++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java @@ -26,8 +26,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteStreams; import com.google.common.net.HttpHeaders; -import com.google.common.reflect.AbstractInvocationHandler; -import com.google.common.reflect.Reflection; import com.palantir.conjure.defs.Conjure; import com.palantir.conjure.java.api.errors.RemoteException; import com.palantir.conjure.java.api.errors.SerializableError; @@ -37,11 +35,9 @@ import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.conjure.java.serialization.ObjectMappers; import com.palantir.conjure.java.services.UndertowServiceGenerator; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.ServiceInstrumenter; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; import com.palantir.conjure.java.undertow.runtime.ConjureHandler; -import com.palantir.conjure.java.undertow.runtime.ConjureSerializerRegistry; +import com.palantir.conjure.java.undertow.runtime.ConjureUndertowRuntime; import com.palantir.conjure.spec.ConjureDefinition; import com.palantir.product.EmptyPathService; import com.palantir.product.EmptyPathServiceEndpoints; @@ -58,13 +54,12 @@ import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; +import io.undertow.server.HttpHandler; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -119,37 +114,19 @@ public UndertowServiceEteTest() { clientConfiguration()); } - private static volatile Method mostRecentMethodInvocation; - @BeforeClass public static void before() { - ServiceContext context = ServiceContext.builder() - .serializerRegistry(ConjureSerializerRegistry.getDefault()) - .serviceInstrumenter(new ServiceInstrumenter() { - @Override - public T instrument(T serviceImplementation, Class serviceInterface) { - return Reflection.newProxy(serviceInterface, new AbstractInvocationHandler() { - @Override - protected Object handleInvocation( - Object proxy, Method method, Object[] args) throws Throwable { - mostRecentMethodInvocation = method; - try { - return method.invoke(serviceImplementation, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - } - }); - } - }) - .build(); + UndertowRuntime context = ConjureUndertowRuntime.builder().build(); - ConjureHandler handler = new ConjureHandler(); - List endpoints = ImmutableList.of( + HttpHandler handler = ConjureHandler.builder().addAllEndpoints(ImmutableList.of( EteServiceEndpoints.of(new UndertowEteResource()), EmptyPathServiceEndpoints.of(() -> true), - EteBinaryServiceEndpoints.of(new UndertowBinaryResource())); - endpoints.forEach(endpoint -> endpoint.create(context).register(handler)); + EteBinaryServiceEndpoints.of(new UndertowBinaryResource())) + .stream() + .flatMap(service -> service.endpoints(context).stream()) + .collect(ImmutableList.toImmutableList())) + .build(); + server = Undertow.builder() .setServerOption(UndertowOptions.DECODE_URL, false) .addHttpListener(8080, "0.0.0.0") @@ -472,13 +449,6 @@ public void testEnumHeaderParameter() { assertThat(client.enumHeader(AuthHeader.valueOf("authHeader"), SimpleEnum.VALUE)).isEqualTo(SimpleEnum.VALUE); } - @Test - public void testInstrumentation() { - assertThat(client.optionalEmpty(AuthHeader.valueOf("authHeader"))) - .isEqualTo(Optional.empty()); - assertThat(mostRecentMethodInvocation.getName()).isEqualTo("optionalEmpty"); - } - @BeforeClass public static void beforeClass() throws IOException { ConjureDefinition def = Conjure.parse(ImmutableList.of( diff --git a/conjure-java-core/src/test/resources/test/api/CookieServiceEndpoints.java.undertow b/conjure-java-core/src/test/resources/test/api/CookieServiceEndpoints.java.undertow index 28cd52496..40e03a2e6 100644 --- a/conjure-java-core/src/test/resources/test/api/CookieServiceEndpoints.java.undertow +++ b/conjure-java-core/src/test/resources/test/api/CookieServiceEndpoints.java.undertow @@ -1,60 +1,76 @@ package test.api; +import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.internal.Auth; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import com.palantir.tokens.auth.BearerToken; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import io.undertow.util.StatusCodes; import java.io.IOException; +import java.util.List; import javax.annotation.Generated; @Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") -public final class CookieServiceEndpoints implements Service { +public final class CookieServiceEndpoints implements UndertowService { private final CookieService delegate; private CookieServiceEndpoints(CookieService delegate) { this.delegate = delegate; } - public static Service of(CookieService delegate) { + public static UndertowService of(CookieService delegate) { return new CookieServiceEndpoints(delegate); } @Override - public Registrable create(ServiceContext context) { - return new CookieServiceRegistrable(context, delegate); + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of(new EatCookiesEndpoint(runtime, delegate)); } - private static final class CookieServiceRegistrable implements Registrable { + private static final class EatCookiesEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + private final CookieService delegate; - private final SerializerRegistry serializers; + EatCookiesEndpoint(UndertowRuntime runtime, CookieService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } - private CookieServiceRegistrable(ServiceContext context, CookieService delegate) { - this.serializers = context.serializerRegistry(); - this.delegate = context.serviceInstrumenter().instrument(delegate, CookieService.class); + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + BearerToken cookieToken = runtime.auth().cookie(exchange, "PALANTIR_TOKEN"); + delegate.eatCookies(cookieToken); + exchange.setStatusCode(StatusCodes.NO_CONTENT); } @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry.add( - Endpoint.get("/cookies", "CookieService", "eatCookies"), - new EatCookiesHandler()); + public HttpString method() { + return Methods.GET; } - private class EatCookiesHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - BearerToken cookieToken = Auth.cookie(exchange, "PALANTIR_TOKEN"); - delegate.eatCookies(cookieToken); - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } + @Override + public String template() { + return "/cookies"; + } + + @Override + public String serviceName() { + return "CookieService"; + } + + @Override + public String name() { + return "eatCookies"; + } + + @Override + public HttpHandler handler() { + return this; } } } diff --git a/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow b/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow index 10ac9cb7b..5ec307b03 100644 --- a/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow +++ b/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow @@ -1,16 +1,13 @@ package com.palantir.another; -import com.google.common.reflect.TypeToken; +import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.undertow.lib.BinaryResponseBody; +import com.palantir.conjure.java.undertow.lib.Deserializer; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.internal.Auth; -import com.palantir.conjure.java.undertow.lib.internal.BinarySerializers; -import com.palantir.conjure.java.undertow.lib.internal.StringDeserializers; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import com.palantir.product.AliasedString; import com.palantir.product.CreateDatasetRequest; import com.palantir.product.NestedAliasedBinary; @@ -21,11 +18,14 @@ import com.palantir.tokens.auth.AuthHeader; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.HeaderMap; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import io.undertow.util.PathTemplateMatch; import io.undertow.util.StatusCodes; import java.io.IOException; import java.io.InputStream; import java.util.Deque; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; @@ -34,440 +34,1068 @@ import java.util.Set; import javax.annotation.Generated; @Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") -public final class TestServiceEndpoints implements Service { +public final class TestServiceEndpoints implements UndertowService { private final TestService delegate; private TestServiceEndpoints(TestService delegate) { this.delegate = delegate; } - public static Service of(TestService delegate) { + public static UndertowService of(TestService delegate) { return new TestServiceEndpoints(delegate); } @Override - public Registrable create(ServiceContext context) { - return new TestServiceRegistrable(context, delegate); + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of( + new GetFileSystemsEndpoint(runtime, delegate), + new CreateDatasetEndpoint(runtime, delegate), + new GetDatasetEndpoint(runtime, delegate), + new GetRawDataEndpoint(runtime, delegate), + new GetAliasedRawDataEndpoint(runtime, delegate), + new MaybeGetRawDataEndpoint(runtime, delegate), + new GetAliasedStringEndpoint(runtime, delegate), + new UploadRawDataEndpoint(runtime, delegate), + new UploadAliasedRawDataEndpoint(runtime, delegate), + new GetBranchesEndpoint(runtime, delegate), + new GetBranchesDeprecatedEndpoint(runtime, delegate), + new ResolveBranchEndpoint(runtime, delegate), + new TestParamEndpoint(runtime, delegate), + new TestQueryParamsEndpoint(runtime, delegate), + new TestNoResponseQueryParamsEndpoint(runtime, delegate), + new TestBooleanEndpoint(runtime, delegate), + new TestDoubleEndpoint(runtime, delegate), + new TestIntegerEndpoint(runtime, delegate), + new TestPostOptionalEndpoint(runtime, delegate), + new TestOptionalIntegerAndDoubleEndpoint(runtime, delegate)); } - private static final class TestServiceRegistrable implements Registrable { + private static final class GetFileSystemsEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + private final TestService delegate; - private final SerializerRegistry serializers; - - private TestServiceRegistrable(ServiceContext context, TestService delegate) { - this.serializers = context.serializerRegistry(); - this.delegate = context.serviceInstrumenter().instrument(delegate, TestService.class); - } - - @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry - .add( - Endpoint.get("/catalog/fileSystems", "TestService", "getFileSystems"), - new GetFileSystemsHandler()) - .add( - Endpoint.post("/catalog/datasets", "TestService", "createDataset"), - new CreateDatasetHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}", "TestService", "getDataset"), - new GetDatasetHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/raw", - "TestService", - "getRawData"), - new GetRawDataHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/raw-aliased", - "TestService", - "getAliasedRawData"), - new GetAliasedRawDataHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/raw-maybe", - "TestService", - "maybeGetRawData"), - new MaybeGetRawDataHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/string-aliased", - "TestService", - "getAliasedString"), - new GetAliasedStringHandler()) - .add( - Endpoint.post( - "/catalog/datasets/upload-raw", "TestService", "uploadRawData"), - new UploadRawDataHandler()) - .add( - Endpoint.post( - "/catalog/datasets/upload-raw-aliased", - "TestService", - "uploadAliasedRawData"), - new UploadAliasedRawDataHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/branches", - "TestService", - "getBranches"), - new GetBranchesHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/branchesDeprecated", - "TestService", - "getBranchesDeprecated"), - new GetBranchesDeprecatedHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/branches/{branch:.+}/resolve", - "TestService", - "resolveBranch"), - new ResolveBranchHandler()) - .add( - Endpoint.get( - "/catalog/datasets/{datasetRid}/testParam", - "TestService", - "testParam"), - new TestParamHandler()) - .add( - Endpoint.post( - "/catalog/test-query-params", "TestService", "testQueryParams"), - new TestQueryParamsHandler()) - .add( - Endpoint.post( - "/catalog/test-no-response-query-params", - "TestService", - "testNoResponseQueryParams"), - new TestNoResponseQueryParamsHandler()) - .add( - Endpoint.get("/catalog/boolean", "TestService", "testBoolean"), - new TestBooleanHandler()) - .add( - Endpoint.get("/catalog/double", "TestService", "testDouble"), - new TestDoubleHandler()) - .add( - Endpoint.get("/catalog/integer", "TestService", "testInteger"), - new TestIntegerHandler()) - .add( - Endpoint.post("/catalog/optional", "TestService", "testPostOptional"), - new TestPostOptionalHandler()) - .add( - Endpoint.get( - "/catalog/optional-integer-double", - "TestService", - "testOptionalIntegerAndDouble"), - new TestOptionalIntegerAndDoubleHandler()); - } - - private class GetFileSystemsHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map result = delegate.getFileSystems(authHeader); - serializers.serialize(result, exchange); - } + private final Serializer> serializer; + + GetFileSystemsEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe() + .serializer(new TypeMarker>() {}); } - private class CreateDatasetHandler implements HttpHandler { - private final TypeToken requestType = - new TypeToken() {}; + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map result = delegate.getFileSystems(authHeader); + serializer.serialize(result, exchange); + } - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - CreateDatasetRequest request = serializers.deserialize(requestType, exchange); - HeaderMap headerParams = exchange.getRequestHeaders(); - String testHeaderArg = - StringDeserializers.deserializeString(headerParams.get("Test-Header")); - Dataset result = delegate.createDataset(authHeader, testHeaderArg, request); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.GET; } - private class GetDatasetHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - Optional result = delegate.getDataset(authHeader, datasetRid); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public String template() { + return "/catalog/fileSystems"; } - private class GetRawDataHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - BinaryResponseBody result = delegate.getRawData(authHeader, datasetRid); - BinarySerializers.serialize(result, exchange); - } + @Override + public String serviceName() { + return "TestService"; } - private class GetAliasedRawDataHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - NestedAliasedBinary result = delegate.getAliasedRawData(authHeader, datasetRid); - serializers.serialize(result, exchange); - } + @Override + public String name() { + return "getFileSystems"; } - private class MaybeGetRawDataHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - Optional result = - delegate.maybeGetRawData(authHeader, datasetRid); - if (result.isPresent()) { - BinarySerializers.serialize(result.get(), exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public HttpHandler handler() { + return this; } + } - private class GetAliasedStringHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - AliasedString result = delegate.getAliasedString(authHeader, datasetRid); - serializers.serialize(result, exchange); - } + private static final class CreateDatasetEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Deserializer deserializer; + + private final Serializer serializer; + + CreateDatasetEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = + runtime.bodySerDe().deserializer(new TypeMarker() {}); + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); } - private class UploadRawDataHandler implements HttpHandler { - private final TypeToken inputType = new TypeToken() {}; + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + CreateDatasetRequest request = deserializer.deserialize(exchange); + HeaderMap headerParams = exchange.getRequestHeaders(); + String testHeaderArg = + runtime.plainSerDe().deserializeString(headerParams.get("Test-Header")); + Dataset result = delegate.createDataset(authHeader, testHeaderArg, request); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/catalog/datasets"; + } - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - InputStream input = BinarySerializers.deserializeInputStream(exchange); - delegate.uploadRawData(authHeader, input); + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "createDataset"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetDatasetEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer> serializer; + + GetDatasetEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + Optional result = delegate.getDataset(authHeader, datasetRid); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class UploadAliasedRawDataHandler implements HttpHandler { - private final TypeToken inputType = - new TypeToken() {}; + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getDataset"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetRawDataEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + GetRawDataEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + BinaryResponseBody result = delegate.getRawData(authHeader, datasetRid); + runtime.bodySerDe().serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/raw"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getRawData"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetAliasedRawDataEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - NestedAliasedBinary input = serializers.deserialize(inputType, exchange); - delegate.uploadAliasedRawData(authHeader, input); + private final Serializer serializer; + + GetAliasedRawDataEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = + runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + NestedAliasedBinary result = delegate.getAliasedRawData(authHeader, datasetRid); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/raw-aliased"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getAliasedRawData"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class MaybeGetRawDataEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + MaybeGetRawDataEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + Optional result = delegate.maybeGetRawData(authHeader, datasetRid); + if (result.isPresent()) { + runtime.bodySerDe().serialize(result.get(), exchange); + } else { exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class GetBranchesHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - Set result = delegate.getBranches(authHeader, datasetRid); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.GET; } - private class GetBranchesDeprecatedHandler implements HttpHandler { - @Override - @SuppressWarnings("deprecation") - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - Set result = delegate.getBranchesDeprecated(authHeader, datasetRid); - serializers.serialize(result, exchange); - } + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/raw-maybe"; } - private class ResolveBranchHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - String branch = StringDeserializers.deserializeString(pathParams.get("branch")); - Optional result = delegate.resolveBranch(authHeader, datasetRid, branch); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public String serviceName() { + return "TestService"; } - private class TestParamHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map pathParams = - exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - ResourceIdentifier datasetRid = - StringDeserializers.deserializeRid(pathParams.get("datasetRid")); - Optional result = delegate.testParam(authHeader, datasetRid); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } + @Override + public String name() { + return "maybeGetRawData"; } - private class TestQueryParamsHandler implements HttpHandler { - private final TypeToken queryType = new TypeToken() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - String query = serializers.deserialize(queryType, exchange); - Map> queryParams = exchange.getQueryParameters(); - ResourceIdentifier something = - StringDeserializers.deserializeRid(queryParams.get("different")); - Optional optionalMiddle = - StringDeserializers.deserializeOptionalRid( - queryParams.get("optionalMiddle")); - ResourceIdentifier implicit = - StringDeserializers.deserializeRid(queryParams.get("implicit")); - Set setEnd = - StringDeserializers.deserializeStringSet(queryParams.get("setEnd")); - Optional optionalEnd = - StringDeserializers.deserializeOptionalRid(queryParams.get("optionalEnd")); - int result = - delegate.testQueryParams( - authHeader, - something, - implicit, - optionalMiddle, - setEnd, - optionalEnd, - query); - serializers.serialize(result, exchange); - } + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetAliasedStringEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer serializer; + + GetAliasedStringEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + AliasedString result = delegate.getAliasedString(authHeader, datasetRid); + serializer.serialize(result, exchange); } - private class TestNoResponseQueryParamsHandler implements HttpHandler { - private final TypeToken queryType = new TypeToken() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - String query = serializers.deserialize(queryType, exchange); - Map> queryParams = exchange.getQueryParameters(); - ResourceIdentifier something = - StringDeserializers.deserializeRid(queryParams.get("different")); - Optional optionalMiddle = - StringDeserializers.deserializeOptionalRid( - queryParams.get("optionalMiddle")); - ResourceIdentifier implicit = - StringDeserializers.deserializeRid(queryParams.get("implicit")); - Set setEnd = - StringDeserializers.deserializeStringSet(queryParams.get("setEnd")); - Optional optionalEnd = - StringDeserializers.deserializeOptionalRid(queryParams.get("optionalEnd")); - delegate.testNoResponseQueryParams( - authHeader, - something, - implicit, - optionalMiddle, - setEnd, - optionalEnd, - query); + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/string-aliased"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getAliasedString"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class UploadRawDataEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + UploadRawDataEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + InputStream input = runtime.bodySerDe().deserializeInputStream(exchange); + delegate.uploadRawData(authHeader, input); + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/catalog/datasets/upload-raw"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "uploadRawData"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class UploadAliasedRawDataEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Deserializer deserializer; + + UploadAliasedRawDataEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = + runtime.bodySerDe().deserializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + NestedAliasedBinary input = deserializer.deserialize(exchange); + delegate.uploadAliasedRawData(authHeader, input); + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/catalog/datasets/upload-raw-aliased"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "uploadAliasedRawData"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetBranchesEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer> serializer; + + GetBranchesEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + Set result = delegate.getBranches(authHeader, datasetRid); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/branches"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getBranches"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class GetBranchesDeprecatedEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer> serializer; + + GetBranchesDeprecatedEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + @SuppressWarnings("deprecation") + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + Set result = delegate.getBranchesDeprecated(authHeader, datasetRid); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/branchesDeprecated"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getBranchesDeprecated"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class ResolveBranchEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer> serializer; + + ResolveBranchEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + String branch = runtime.plainSerDe().deserializeString(pathParams.get("branch")); + Optional result = delegate.resolveBranch(authHeader, datasetRid, branch); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class TestBooleanHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - boolean result = delegate.testBoolean(authHeader); - serializers.serialize(result, exchange); - } + @Override + public HttpString method() { + return Methods.GET; } - private class TestDoubleHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - double result = delegate.testDouble(authHeader); - serializers.serialize(result, exchange); - } + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/branches/{branch:.+}/resolve"; } - private class TestIntegerHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - int result = delegate.testInteger(authHeader); - serializers.serialize(result, exchange); - } + @Override + public String serviceName() { + return "TestService"; } - private class TestPostOptionalHandler implements HttpHandler { - private final TypeToken> maybeStringType = - new TypeToken>() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Optional maybeString = serializers.deserialize(maybeStringType, exchange); - Optional result = delegate.testPostOptional(authHeader, maybeString); - if (result.isPresent()) { - serializers.serialize(result, exchange); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } + @Override + public String name() { + return "resolveBranch"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestParamEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer> serializer; + + TestParamEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map pathParams = + exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); + ResourceIdentifier datasetRid = + runtime.plainSerDe().deserializeRid(pathParams.get("datasetRid")); + Optional result = delegate.testParam(authHeader, datasetRid); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { + exchange.setStatusCode(StatusCodes.NO_CONTENT); } } - private class TestOptionalIntegerAndDoubleHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - AuthHeader authHeader = Auth.header(exchange); - Map> queryParams = exchange.getQueryParameters(); - OptionalInt maybeInteger = - StringDeserializers.deserializeOptionalInteger( - queryParams.get("maybeInteger")); - OptionalDouble maybeDouble = - StringDeserializers.deserializeOptionalDouble( - queryParams.get("maybeDouble")); - delegate.testOptionalIntegerAndDouble(authHeader, maybeInteger, maybeDouble); + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/datasets/{datasetRid}/testParam"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testParam"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestQueryParamsEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Deserializer deserializer; + + private final Serializer serializer; + + TestQueryParamsEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = runtime.bodySerDe().deserializer(new TypeMarker() {}); + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + String query = deserializer.deserialize(exchange); + Map> queryParams = exchange.getQueryParameters(); + ResourceIdentifier something = + runtime.plainSerDe().deserializeRid(queryParams.get("different")); + Optional optionalMiddle = + runtime.plainSerDe().deserializeOptionalRid(queryParams.get("optionalMiddle")); + ResourceIdentifier implicit = + runtime.plainSerDe().deserializeRid(queryParams.get("implicit")); + Set setEnd = + runtime.plainSerDe().deserializeStringSet(queryParams.get("setEnd")); + Optional optionalEnd = + runtime.plainSerDe().deserializeOptionalRid(queryParams.get("optionalEnd")); + int result = + delegate.testQueryParams( + authHeader, + something, + implicit, + optionalMiddle, + setEnd, + optionalEnd, + query); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/catalog/test-query-params"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testQueryParams"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestNoResponseQueryParamsEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Deserializer deserializer; + + TestNoResponseQueryParamsEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = runtime.bodySerDe().deserializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + String query = deserializer.deserialize(exchange); + Map> queryParams = exchange.getQueryParameters(); + ResourceIdentifier something = + runtime.plainSerDe().deserializeRid(queryParams.get("different")); + Optional optionalMiddle = + runtime.plainSerDe().deserializeOptionalRid(queryParams.get("optionalMiddle")); + ResourceIdentifier implicit = + runtime.plainSerDe().deserializeRid(queryParams.get("implicit")); + Set setEnd = + runtime.plainSerDe().deserializeStringSet(queryParams.get("setEnd")); + Optional optionalEnd = + runtime.plainSerDe().deserializeOptionalRid(queryParams.get("optionalEnd")); + delegate.testNoResponseQueryParams( + authHeader, something, implicit, optionalMiddle, setEnd, optionalEnd, query); + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/catalog/test-no-response-query-params"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testNoResponseQueryParams"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestBooleanEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer serializer; + + TestBooleanEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + boolean result = delegate.testBoolean(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/boolean"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testBoolean"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestDoubleEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer serializer; + + TestDoubleEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + double result = delegate.testDouble(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/double"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testDouble"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestIntegerEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Serializer serializer; + + TestIntegerEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + int result = delegate.testInteger(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/integer"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testInteger"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestPostOptionalEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + private final Deserializer> deserializer; + + private final Serializer> serializer; + + TestPostOptionalEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.deserializer = + runtime.bodySerDe().deserializer(new TypeMarker>() {}); + this.serializer = runtime.bodySerDe().serializer(new TypeMarker>() {}); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Optional maybeString = deserializer.deserialize(exchange); + Optional result = delegate.testPostOptional(authHeader, maybeString); + if (result.isPresent()) { + serializer.serialize(result, exchange); + } else { exchange.setStatusCode(StatusCodes.NO_CONTENT); } } + + @Override + public HttpString method() { + return Methods.POST; + } + + @Override + public String template() { + return "/catalog/optional"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testPostOptional"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestOptionalIntegerAndDoubleEndpoint + implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final TestService delegate; + + TestOptionalIntegerAndDoubleEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + AuthHeader authHeader = runtime.auth().header(exchange); + Map> queryParams = exchange.getQueryParameters(); + OptionalInt maybeInteger = + runtime.plainSerDe() + .deserializeOptionalInteger(queryParams.get("maybeInteger")); + OptionalDouble maybeDouble = + runtime.plainSerDe().deserializeOptionalDouble(queryParams.get("maybeDouble")); + delegate.testOptionalIntegerAndDouble(authHeader, maybeInteger, maybeDouble); + exchange.setStatusCode(StatusCodes.NO_CONTENT); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/catalog/optional-integer-double"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "testOptionalIntegerAndDouble"; + } + + @Override + public HttpHandler handler() { + return this; + } } } diff --git a/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow.binary b/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow.binary index 787e7e896..cad1dbd1b 100644 --- a/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow.binary +++ b/conjure-java-core/src/test/resources/test/api/TestServiceEndpoints.java.undertow.binary @@ -1,57 +1,74 @@ package test.api; +import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.undertow.lib.BinaryResponseBody; import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; -import com.palantir.conjure.java.undertow.lib.internal.BinarySerializers; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import java.io.IOException; +import java.util.List; import javax.annotation.Generated; @Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") -public final class TestServiceEndpoints implements Service { +public final class TestServiceEndpoints implements UndertowService { private final TestService delegate; private TestServiceEndpoints(TestService delegate) { this.delegate = delegate; } - public static Service of(TestService delegate) { + public static UndertowService of(TestService delegate) { return new TestServiceEndpoints(delegate); } @Override - public Registrable create(ServiceContext context) { - return new TestServiceRegistrable(context, delegate); + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of(new GetBinaryEndpoint(runtime, delegate)); } - private static final class TestServiceRegistrable implements Registrable { + private static final class GetBinaryEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + private final TestService delegate; - private final SerializerRegistry serializers; + GetBinaryEndpoint(UndertowRuntime runtime, TestService delegate) { + this.runtime = runtime; + this.delegate = delegate; + } - private TestServiceRegistrable(ServiceContext context, TestService delegate) { - this.serializers = context.serializerRegistry(); - this.delegate = context.serviceInstrumenter().instrument(delegate, TestService.class); + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException { + BinaryResponseBody result = delegate.getBinary(); + runtime.bodySerDe().serialize(result, exchange); } @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry.add( - Endpoint.get("/", "TestService", "getBinary"), new GetBinaryHandler()); + public HttpString method() { + return Methods.GET; } - private class GetBinaryHandler implements HttpHandler { - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - BinaryResponseBody result = delegate.getBinary(); - BinarySerializers.serialize(result, exchange); - } + @Override + public String template() { + return "/"; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "getBinary"; + } + + @Override + public HttpHandler handler() { + return this; } } } diff --git a/conjure-java-server-verifier/src/test/java/com/palantir/conjure/java/verification/server/undertest/UndertowServerUnderTestRule.java b/conjure-java-server-verifier/src/test/java/com/palantir/conjure/java/verification/server/undertest/UndertowServerUnderTestRule.java index ab5f7a28c..fe28d53c7 100644 --- a/conjure-java-server-verifier/src/test/java/com/palantir/conjure/java/verification/server/undertest/UndertowServerUnderTestRule.java +++ b/conjure-java-server-verifier/src/test/java/com/palantir/conjure/java/verification/server/undertest/UndertowServerUnderTestRule.java @@ -17,14 +17,15 @@ package com.palantir.conjure.java.verification.server.undertest; import com.google.common.reflect.Reflection; -import com.palantir.conjure.java.undertow.lib.Service; -import com.palantir.conjure.java.undertow.lib.ServiceContext; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; import com.palantir.conjure.java.undertow.runtime.ConjureHandler; -import com.palantir.conjure.java.undertow.runtime.ConjureSerializerRegistry; +import com.palantir.conjure.java.undertow.runtime.ConjureUndertowRuntime; import com.palantir.conjure.verification.client.AutoDeserializeServiceEndpoints; import com.palantir.conjure.verification.client.UndertowAutoDeserializeService; import io.undertow.Handlers; import io.undertow.Undertow; +import io.undertow.server.HttpHandler; import org.junit.rules.ExternalResource; public final class UndertowServerUnderTestRule extends ExternalResource { @@ -34,17 +35,16 @@ public final class UndertowServerUnderTestRule extends ExternalResource { private Undertow server; @Override - protected void before() throws Throwable { - UndertowAutoDeserializeService service = Reflection.newProxy( + protected void before() { + UndertowAutoDeserializeService autoDeserialize = Reflection.newProxy( UndertowAutoDeserializeService.class, new EchoResourceInvocationHandler()); - Service endpoints = AutoDeserializeServiceEndpoints.of(service); + UndertowService service = AutoDeserializeServiceEndpoints.of(autoDeserialize); - ConjureHandler handler = new ConjureHandler(); - ServiceContext context = ServiceContext.builder() - .serializerRegistry(ConjureSerializerRegistry.getDefault()) - .build(); + UndertowRuntime context = ConjureUndertowRuntime.builder().build(); - endpoints.create(context).register(handler); + HttpHandler handler = ConjureHandler.builder() + .addAllEndpoints(service.endpoints(context)) + .build(); server = Undertow.builder() .addHttpListener(PORT, "0.0.0.0") diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Attachments.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Attachments.java similarity index 88% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Attachments.java rename to conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Attachments.java index 2bc20f0e1..d7be33759 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Attachments.java +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Attachments.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.undertow.lib; +package com.palantir.conjure.java.undertow.runtime; import com.palantir.tokens.auth.UnverifiedJsonWebToken; import io.undertow.util.AttachmentKey; diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/Auth.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureAuthorizationExtractor.java similarity index 75% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/Auth.java rename to conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureAuthorizationExtractor.java index 35f5b15f2..1bb56f400 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/Auth.java +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureAuthorizationExtractor.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.palantir.conjure.java.undertow.lib.internal; +package com.palantir.conjure.java.undertow.runtime; -import com.palantir.conjure.java.undertow.lib.Attachments; +import com.palantir.conjure.java.undertow.lib.AuthorizationExtractor; +import com.palantir.conjure.java.undertow.lib.PlainSerDe; import com.palantir.logsafe.Preconditions; import com.palantir.tokens.auth.AuthHeader; import com.palantir.tokens.auth.BearerToken; @@ -29,21 +30,26 @@ import org.slf4j.MDC; /** - * Provides utility methods to parse auth types. + * Implementation of {@link AuthorizationExtractor} which sets thread state based on a parsed + * {@link UnverifiedJsonWebToken}. This behavior requires invocations to be wrapped with the + * {@link LoggingContextHandler} to avoid leaking {@link MDC} state to other operations. + * + * Package private internal API. */ -public final class Auth { +final class ConjureAuthorizationExtractor implements AuthorizationExtractor { - private static final String USER_ID_KEY = "userId"; - private static final String SESSION_ID_KEY = "sessionId"; - private static final String TOKEN_ID_KEY = "tokenId"; - private static Consumer sessionIdSetter = sessionId -> MDC.put(SESSION_ID_KEY, sessionId); - private static Consumer tokenIdSetter = tokenId -> MDC.put(TOKEN_ID_KEY, tokenId); + private final PlainSerDe plainSerDe; + + ConjureAuthorizationExtractor(PlainSerDe plainSerDe) { + this.plainSerDe = plainSerDe; + } /** * Parses an {@link AuthHeader} from the provided {@link HttpServerExchange} and applies * {@link UnverifiedJsonWebToken} information to the {@link MDC thread state} if present. */ - public static AuthHeader header(HttpServerExchange exchange) { + @Override + public AuthHeader header(HttpServerExchange exchange) { HeaderValues authorization = exchange.getRequestHeaders().get(Headers.AUTHORIZATION); // Do not use Iterables.getOnlyElement because it includes values in the exception message. // We do not want credential material logged to disk, even if it's marked unsafe. @@ -56,11 +62,18 @@ public static AuthHeader header(HttpServerExchange exchange) { * Parses a {@link BearerToken} from the provided {@link HttpServerExchange} and applies * {@link UnverifiedJsonWebToken} information to the {@link MDC thread state} if present. */ - public static BearerToken cookie(HttpServerExchange exchange, String cookieName) { - return setState(exchange, StringDeserializers.deserializeBearerToken( - exchange.getRequestCookies().get(cookieName).getValue())); + @Override + public BearerToken cookie(HttpServerExchange exchange, String cookieName) { + return setState(exchange, + plainSerDe.deserializeBearerToken(exchange.getRequestCookies().get(cookieName).getValue())); } + private static final String USER_ID_KEY = "userId"; + private static final String SESSION_ID_KEY = "sessionId"; + private static final String TOKEN_ID_KEY = "tokenId"; + private static Consumer sessionIdSetter = sessionId -> MDC.put(SESSION_ID_KEY, sessionId); + private static Consumer tokenIdSetter = tokenId -> MDC.put(TOKEN_ID_KEY, tokenId); + /** * Attempts to extract a {@link UnverifiedJsonWebToken JSON Web Token} from the * {@link BearerToken} value, and populates the SLF4J {@link MDC} with @@ -83,6 +96,4 @@ private static AuthHeader setState(HttpServerExchange exchange, AuthHeader authH setState(exchange, authHeader.getBearerToken()); return authHeader; } - - private Auth() {} } diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureBodySerDe.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureBodySerDe.java new file mode 100644 index 000000000..20825a3ba --- /dev/null +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureBodySerDe.java @@ -0,0 +1,179 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.runtime; + +import com.google.common.collect.ImmutableList; +import com.palantir.conjure.java.undertow.lib.BinaryResponseBody; +import com.palantir.conjure.java.undertow.lib.BodySerDe; +import com.palantir.conjure.java.undertow.lib.Deserializer; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.logsafe.Preconditions; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderValues; +import io.undertow.util.Headers; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** Package private internal API. */ +final class ConjureBodySerDe implements BodySerDe { + + private static final String BINARY_CONTENT_TYPE = "application/octet-stream"; + + private final List encodings; + + /** + * Selects the first (based on input order) of the provided encodings that + * {@link Encoding#supportsContentType supports} the serialization format {@link Headers#ACCEPT accepted} + * by a given request, or the first serializer if no such serializer can be found. + */ + ConjureBodySerDe(List encodings) { + // Defensive copy + this.encodings = ImmutableList.copyOf(encodings); + Preconditions.checkArgument(encodings.size() > 0, "At least one Encoding is required"); + } + + @Override + public Serializer serializer(TypeMarker token) { + return new EncodingSerializerRegistry<>(encodings, token); + } + + @Override + public Deserializer deserializer(TypeMarker token) { + return new EncodingDeserializerRegistry<>(encodings, token); + } + + @Override + public void serialize(BinaryResponseBody value, HttpServerExchange exchange) throws IOException { + Preconditions.checkNotNull(value, "A BinaryResponseBody value is required"); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, BINARY_CONTENT_TYPE); + value.write(exchange.getOutputStream()); + } + + @Override + public InputStream deserializeInputStream(HttpServerExchange exchange) { + String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + if (contentType == null) { + throw new SafeIllegalArgumentException("Request is missing Content-Type header"); + } + if (!contentType.startsWith(BINARY_CONTENT_TYPE)) { + throw new SafeIllegalArgumentException("Unsupported Content-Type", + SafeArg.of("Content-Type", contentType)); + } + return exchange.getInputStream(); + } + + private static final class EncodingSerializerRegistry implements Serializer { + + private final EncodingSerializerContainer defaultEncoding; + private final List> encodings; + + EncodingSerializerRegistry(List encodings, TypeMarker token) { + this.encodings = encodings.stream() + .map(encoding -> new EncodingSerializerContainer<>(encoding, token)) + .collect(ImmutableList.toImmutableList()); + this.defaultEncoding = this.encodings.get(0); + } + + @Override + public void serialize(T value, HttpServerExchange exchange) throws IOException { + Preconditions.checkNotNull(value, "cannot serialize null value"); + EncodingSerializerContainer container = getResponseSerializer(exchange); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, container.encoding.getContentType()); + container.serializer.serialize(value, exchange.getOutputStream()); + } + + /** Returns the {@link EncodingSerializerContainer} to use for the exchange response. */ + @SuppressWarnings("ForLoopReplaceableByForEach") // performance sensitive code avoids iterator allocation + EncodingSerializerContainer getResponseSerializer(HttpServerExchange exchange) { + HeaderValues acceptValues = exchange.getRequestHeaders().get(Headers.ACCEPT); + if (acceptValues != null) { + // This implementation prefers the client "Accept" order + for (int i = 0; i < acceptValues.size(); i++) { + String acceptValue = acceptValues.get(i); + for (int j = 0; j < encodings.size(); j++) { + EncodingSerializerContainer container = encodings.get(j); + if (container.encoding.supportsContentType(acceptValue)) { + return container; + } + } + } + } + // Fall back to the default + return defaultEncoding; + } + } + + private static final class EncodingSerializerContainer { + + private final Encoding encoding; + private final Encoding.Serializer serializer; + + EncodingSerializerContainer(Encoding encoding, TypeMarker token) { + this.encoding = encoding; + this.serializer = encoding.serializer(token); + } + } + + private static final class EncodingDeserializerRegistry implements Deserializer { + + private final List> encodings; + + EncodingDeserializerRegistry(List encodings, TypeMarker token) { + this.encodings = encodings.stream() + .map(encoding -> new EncodingDeserializerContainer<>(encoding, token)) + .collect(ImmutableList.toImmutableList()); + } + + @Override + public T deserialize(HttpServerExchange exchange) throws IOException { + EncodingDeserializerContainer container = getRequestDeserializer(exchange); + return container.deserializer.deserialize(exchange.getInputStream()); + } + + /** Returns the {@link EncodingDeserializerContainer} to use to deserialize the request body. */ + @SuppressWarnings("ForLoopReplaceableByForEach") // performance sensitive code avoids iterator allocation + EncodingDeserializerContainer getRequestDeserializer(HttpServerExchange exchange) { + String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + if (contentType == null) { + throw new SafeIllegalArgumentException("Request is missing Content-Type header"); + } + for (int i = 0; i < encodings.size(); i++) { + EncodingDeserializerContainer container = encodings.get(i); + if (container.encoding.supportsContentType(contentType)) { + return container; + } + } + throw FrameworkException.unsupportedMediaType("Unsupported Content-Type", + SafeArg.of("Content-Type", contentType)); + } + } + + private static final class EncodingDeserializerContainer { + + private final Encoding encoding; + private final Encoding.Deserializer deserializer; + + EncodingDeserializerContainer(Encoding encoding, TypeMarker token) { + this.encoding = encoding; + this.deserializer = encoding.deserializer(token); + } + } +} diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandler.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandler.java index e16da0261..95ae56273 100644 --- a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandler.java +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandler.java @@ -21,7 +21,8 @@ import com.palantir.conjure.java.api.errors.RemoteException; import com.palantir.conjure.java.api.errors.SerializableError; import com.palantir.conjure.java.api.errors.ServiceException; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; import com.palantir.logsafe.SafeArg; import io.undertow.io.UndertowOutputStream; import io.undertow.server.HttpHandler; @@ -30,6 +31,7 @@ import java.io.IOException; import java.io.OutputStream; import java.time.temporal.ChronoUnit; +import java.util.Collections; import java.util.Optional; import java.util.function.Consumer; import org.slf4j.Logger; @@ -38,26 +40,21 @@ /** * Delegates to the given {@link HttpHandler}, and catches&forwards all {@link Throwable}s. Any exception thrown in - * the delegate handler is caught and serialized using the configured {@link SerializerRegistry} into a - * {@link SerializableError}. The result is written it into the exchange's output stream, and an appropriate HTTP - * status code is set. + * the delegate handler is caught and serialized using the Conjure JSON format into a {@link SerializableError}. The + * result is written it into the exchange's output stream, and an appropriate HTTP status code is set. */ final class ConjureExceptionHandler implements HttpHandler { private static final Logger log = LoggerFactory.getLogger(ConjureExceptionHandler.class); - // Exceptions should always be serialized using JSON - private static final SerializerRegistry DEFAULT_SERIALIZERS = new ConjureSerializerRegistry(Serializers.json()); - private final SerializerRegistry serializers; + // Exceptions should always be serialized using JSON + private final Serializer serializer = + new ConjureBodySerDe(Collections.singletonList(Encodings.json())) + .serializer(new TypeMarker() {}); private final HttpHandler delegate; - ConjureExceptionHandler(SerializerRegistry serializers, HttpHandler delegate) { - this.serializers = serializers; - this.delegate = delegate; - } - ConjureExceptionHandler(HttpHandler delegate) { - this(DEFAULT_SERIALIZERS, delegate); + this.delegate = delegate; } @Override @@ -151,7 +148,7 @@ private void writeResponse(HttpServerExchange exchange, Optional> WRAPPERS = ImmutableList.>of( @@ -64,12 +68,9 @@ public final class ConjureHandler implements HttpHandler, EndpointRegistry { private final RoutingHandler routingHandler; - public ConjureHandler(HttpHandler fallback) { + private ConjureHandler(HttpHandler fallback, List endpoints) { this.routingHandler = Handlers.routing().setFallbackHandler(fallback); - } - - public ConjureHandler() { - this(ResponseCodeHandler.HANDLE_404); + endpoints.forEach(this::register); } @Override @@ -77,14 +78,52 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { routingHandler.handleRequest(exchange); } - @Override - public ConjureHandler add(Endpoint endpoint, HttpHandler handler) { - HttpHandler current = handler; + private void register(Endpoint endpoint) { + HttpHandler current = endpoint.handler(); for (BiFunction wrapper : WRAPPERS) { current = wrapper.apply(endpoint, current); } routingHandler.add(endpoint.method(), endpoint.template(), current); - return this; } + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private final List endpoints = Lists.newArrayList(); + private HttpHandler fallback = ResponseCodeHandler.HANDLE_404; + + private Builder() { } + + @CanIgnoreReturnValue + public Builder endpoints(Endpoint value) { + endpoints.add(Preconditions.checkNotNull(value, "Value is required")); + return this; + } + + @CanIgnoreReturnValue + public Builder addAllEndpoints(Iterable values) { + Preconditions.checkNotNull(values, "Values is required"); + for (Endpoint endpoint : values) { + endpoints(endpoint); + } + return this; + } + + /** + * The fallback {@link HttpHandler handler} is invoked when no {@link Endpoint} matches a request. + * By default a 404 response status will be served. + */ + @CanIgnoreReturnValue + public Builder fallback(HttpHandler value) { + fallback = Preconditions.checkNotNull(value, "Value is required"); + return this; + } + + public HttpHandler build() { + return new ConjureHandler(fallback, endpoints); + } + } } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/StringDeserializers.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjurePlainSerDe.java similarity index 64% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/StringDeserializers.java rename to conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjurePlainSerDe.java index 13511db66..6b3c370f6 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/StringDeserializers.java +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjurePlainSerDe.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.palantir.conjure.java.undertow.lib.internal; +package com.palantir.conjure.java.undertow.runtime; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.undertow.lib.PlainSerDe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.UnsafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; @@ -38,14 +39,13 @@ import java.util.function.Function; import javax.annotation.Nullable; -// TODO(nmiyake): figure out exception handling. Currently, throws empty IllegalArgumentException on any failure. -// Should method signatures be changed to include name of parameter or should exception handling be done in generated -// code? -public final class StringDeserializers { +/** Package private internal API. */ +enum ConjurePlainSerDe implements PlainSerDe { + INSTANCE; - private StringDeserializers() {} - - public static BearerToken deserializeBearerToken(String in) { + @Override + public BearerToken deserializeBearerToken(@Nullable String in) { + checkArgumentNotNull(in); try { return BearerToken.valueOf(in); } catch (RuntimeException ex) { @@ -53,19 +53,22 @@ public static BearerToken deserializeBearerToken(String in) { } } - public static BearerToken deserializeBearerToken(Iterable in) { + @Override + public BearerToken deserializeBearerToken(@Nullable Iterable in) { // BearerToken values should never be logged return deserializeBearerToken(getOnlyElementDoNotLogValues(in)); } - public static Optional deserializeOptionalBearerToken(@Nullable String in) { - if (Strings.isNullOrEmpty(in)) { + @Override + public Optional deserializeOptionalBearerToken(@Nullable String in) { + if (in == null) { return Optional.empty(); } return Optional.of(deserializeBearerToken(in)); } - public static Optional deserializeOptionalBearerToken(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalBearerToken(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } @@ -73,7 +76,8 @@ public static Optional deserializeOptionalBearerToken(@Nullable Ite return Optional.of(deserializeBearerToken(getOnlyElementDoNotLogValues(in))); } - public static List deserializeBearerTokenList(@Nullable Iterable in) { + @Override + public List deserializeBearerTokenList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -84,7 +88,8 @@ public static List deserializeBearerTokenList(@Nullable Iterable deserializeBearerTokenSet(@Nullable Iterable in) { + @Override + public Set deserializeBearerTokenSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -95,7 +100,9 @@ public static Set deserializeBearerTokenSet(@Nullable Iterable in) { + @Override + public boolean deserializeBoolean(@Nullable Iterable in) { return deserializeBoolean(getOnlyElement(in)); } - public static Optional deserializeOptionalBoolean(String in) { + @Override + public Optional deserializeOptionalBoolean(@Nullable String in) { + if (in == null) { + return Optional.empty(); + } return Optional.of(deserializeBoolean(in)); } - public static Optional deserializeOptionalBoolean(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalBoolean(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } return Optional.of(deserializeBoolean(getOnlyElement(in))); } - public static List deserializeBooleanList(@Nullable Iterable in) { + @Override + public List deserializeBooleanList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -129,7 +143,8 @@ public static List deserializeBooleanList(@Nullable Iterable in return builder.build(); } - public static Set deserializeBooleanSet(@Nullable Iterable in) { + @Override + public Set deserializeBooleanSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -140,7 +155,9 @@ public static Set deserializeBooleanSet(@Nullable Iterable in) return builder.build(); } - public static OffsetDateTime deserializeDateTime(String in) { + @Override + public OffsetDateTime deserializeDateTime(@Nullable String in) { + checkArgumentNotNull(in); try { return OffsetDateTime.parse(in); } catch (RuntimeException ex) { @@ -148,22 +165,29 @@ public static OffsetDateTime deserializeDateTime(String in) { } } - public static OffsetDateTime deserializeDateTime(@Nullable Iterable in) { + @Override + public OffsetDateTime deserializeDateTime(@Nullable Iterable in) { return deserializeDateTime(getOnlyElement(in)); } - public static Optional deserializeOptionalDateTime(String in) { + @Override + public Optional deserializeOptionalDateTime(@Nullable String in) { + if (in == null) { + return Optional.empty(); + } return Optional.of(deserializeDateTime(in)); } - public static Optional deserializeOptionalDateTime(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalDateTime(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } return Optional.of(deserializeDateTime(getOnlyElement(in))); } - public static List deserializeDateTimeList(@Nullable Iterable in) { + @Override + public List deserializeDateTimeList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -174,7 +198,8 @@ public static List deserializeDateTimeList(@Nullable Iterable deserializeDateTimeSet(@Nullable Iterable in) { + @Override + public Set deserializeDateTimeSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -185,7 +210,9 @@ public static Set deserializeDateTimeSet(@Nullable Iterable in) { + @Override + public double deserializeDouble(@Nullable Iterable in) { return deserializeDouble(getOnlyElement(in)); } - public static OptionalDouble deserializeOptionalDouble(String in) { + @Override + public OptionalDouble deserializeOptionalDouble(@Nullable String in) { + if (in == null) { + return OptionalDouble.empty(); + } return OptionalDouble.of(deserializeDouble(in)); } - public static OptionalDouble deserializeOptionalDouble(@Nullable Iterable in) { + @Override + public OptionalDouble deserializeOptionalDouble(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return OptionalDouble.empty(); } return OptionalDouble.of(deserializeDouble(getOnlyElement(in))); } - public static List deserializeDoubleList(@Nullable Iterable in) { + @Override + public List deserializeDoubleList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -219,7 +253,8 @@ public static List deserializeDoubleList(@Nullable Iterable in) return builder.build(); } - public static Set deserializeDoubleSet(@Nullable Iterable in) { + @Override + public Set deserializeDoubleSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -230,7 +265,9 @@ public static Set deserializeDoubleSet(@Nullable Iterable in) { return builder.build(); } - public static int deserializeInteger(String in) { + @Override + public int deserializeInteger(@Nullable String in) { + checkArgumentNotNull(in); try { return Integer.parseInt(in); } catch (RuntimeException ex) { @@ -238,22 +275,29 @@ public static int deserializeInteger(String in) { } } - public static int deserializeInteger(@Nullable Iterable in) { + @Override + public int deserializeInteger(@Nullable Iterable in) { return deserializeInteger(getOnlyElement(in)); } - public static OptionalInt deserializeOptionalInteger(String in) { + @Override + public OptionalInt deserializeOptionalInteger(@Nullable String in) { + if (in == null) { + return OptionalInt.empty(); + } return OptionalInt.of(deserializeInteger(in)); } - public static OptionalInt deserializeOptionalInteger(@Nullable Iterable in) { + @Override + public OptionalInt deserializeOptionalInteger(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return OptionalInt.empty(); } return OptionalInt.of(deserializeInteger(getOnlyElement(in))); } - public static List deserializeIntegerList(@Nullable Iterable in) { + @Override + public List deserializeIntegerList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -264,7 +308,8 @@ public static List deserializeIntegerList(@Nullable Iterable in return builder.build(); } - public static Set deserializeIntegerSet(@Nullable Iterable in) { + @Override + public Set deserializeIntegerSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -275,7 +320,9 @@ public static Set deserializeIntegerSet(@Nullable Iterable in) return builder.build(); } - public static ResourceIdentifier deserializeRid(String in) { + @Override + public ResourceIdentifier deserializeRid(@Nullable String in) { + checkArgumentNotNull(in); try { return ResourceIdentifier.valueOf(in); } catch (RuntimeException ex) { @@ -283,22 +330,29 @@ public static ResourceIdentifier deserializeRid(String in) { } } - public static ResourceIdentifier deserializeRid(@Nullable Iterable in) { + @Override + public ResourceIdentifier deserializeRid(@Nullable Iterable in) { return deserializeRid(getOnlyElement(in)); } - public static Optional deserializeOptionalRid(String in) { + @Override + public Optional deserializeOptionalRid(@Nullable String in) { + if (in == null) { + return Optional.empty(); + } return Optional.of(deserializeRid(in)); } - public static Optional deserializeOptionalRid(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalRid(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } return Optional.of(deserializeRid(getOnlyElement(in))); } - public static List deserializeRidList(@Nullable Iterable in) { + @Override + public List deserializeRidList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -309,7 +363,8 @@ public static List deserializeRidList(@Nullable Iterable deserializeRidSet(@Nullable Iterable in) { + @Override + public Set deserializeRidSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -320,7 +375,9 @@ public static Set deserializeRidSet(@Nullable Iterable in) { + @Override + public SafeLong deserializeSafeLong(@Nullable Iterable in) { return deserializeSafeLong(getOnlyElement(in)); } - public static Optional deserializeOptionalSafeLong(String in) { + @Override + public Optional deserializeOptionalSafeLong(@Nullable String in) { + if (in == null) { + return Optional.empty(); + } return Optional.of(deserializeSafeLong(in)); } - public static Optional deserializeOptionalSafeLong(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalSafeLong(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } return Optional.of(deserializeSafeLong(getOnlyElement(in))); } - public static List deserializeSafeLongList(@Nullable Iterable in) { + @Override + public List deserializeSafeLongList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -354,7 +418,8 @@ public static List deserializeSafeLongList(@Nullable Iterable return builder.build(); } - public static Set deserializeSafeLongSet(@Nullable Iterable in) { + @Override + public Set deserializeSafeLongSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -365,34 +430,45 @@ public static Set deserializeSafeLongSet(@Nullable Iterable in return builder.build(); } - public static String deserializeString(String in) { - return in; + @Override + public String deserializeString(@Nullable String in) { + return checkArgumentNotNull(in); } - public static String deserializeString(@Nullable Iterable in) { + @Override + public String deserializeString(@Nullable Iterable in) { return deserializeString(getOnlyElement(in)); } - public static Optional deserializeOptionalString(String in) { + @Override + public Optional deserializeOptionalString(@Nullable String in) { + if (in == null) { + return Optional.empty(); + } return Optional.of(deserializeString(in)); } - public static Optional deserializeOptionalString(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalString(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } return Optional.of(deserializeString(getOnlyElement(in))); } - public static List deserializeStringList(@Nullable Iterable in) { + @Override + public List deserializeStringList(@Nullable Iterable in) { return in == null ? Collections.emptyList() : ImmutableList.copyOf(in); } - public static Set deserializeStringSet(@Nullable Iterable in) { + @Override + public Set deserializeStringSet(@Nullable Iterable in) { return in == null ? Collections.emptySet() : ImmutableSet.copyOf(in); } - public static UUID deserializeUuid(String in) { + @Override + public UUID deserializeUuid(@Nullable String in) { + checkArgumentNotNull(in); try { return UUID.fromString(in); } catch (RuntimeException ex) { @@ -400,22 +476,29 @@ public static UUID deserializeUuid(String in) { } } - public static UUID deserializeUuid(@Nullable Iterable in) { + @Override + public UUID deserializeUuid(@Nullable Iterable in) { return deserializeUuid(getOnlyElement(in)); } - public static Optional deserializeOptionalUuid(String in) { + @Override + public Optional deserializeOptionalUuid(@Nullable String in) { + if (in == null) { + return Optional.empty(); + } return Optional.of(deserializeUuid(in)); } - public static Optional deserializeOptionalUuid(@Nullable Iterable in) { + @Override + public Optional deserializeOptionalUuid(@Nullable Iterable in) { if (in == null || Iterables.isEmpty(in)) { return Optional.empty(); } return Optional.of(deserializeUuid(getOnlyElement(in))); } - public static List deserializeUuidList(@Nullable Iterable in) { + @Override + public List deserializeUuidList(@Nullable Iterable in) { if (in == null) { return Collections.emptyList(); } @@ -426,7 +509,8 @@ public static List deserializeUuidList(@Nullable Iterable in) { return builder.build(); } - public static Set deserializeUuidSet(@Nullable Iterable in) { + @Override + public Set deserializeUuidSet(@Nullable Iterable in) { if (in == null) { return Collections.emptySet(); } @@ -437,20 +521,24 @@ public static Set deserializeUuidSet(@Nullable Iterable in) { return builder.build(); } - public static T deserializeComplex(String in, Function factory) { - return factory.apply(deserializeString(in)); + @Override + public T deserializeComplex(@Nullable String in, Function factory) { + return factory.apply(deserializeString(checkArgumentNotNull(in))); } - public static T deserializeComplex(@Nullable Iterable in, Function factory) { + @Override + public T deserializeComplex(@Nullable Iterable in, Function factory) { return factory.apply(deserializeString(in)); } - public static Optional deserializeOptionalComplex( + @Override + public Optional deserializeOptionalComplex( @Nullable Iterable in, Function factory) { return deserializeOptionalString(in).map(factory); } - public static List deserializeComplexList(@Nullable Iterable in, Function factory) { + @Override + public List deserializeComplexList(@Nullable Iterable in, Function factory) { if (in == null) { return Collections.emptyList(); } @@ -461,7 +549,8 @@ public static List deserializeComplexList(@Nullable Iterable in, return builder.build(); } - public static Set deserializeComplexSet(@Nullable Iterable in, Function factory) { + @Override + public Set deserializeComplexSet(@Nullable Iterable in, Function factory) { if (in == null) { return Collections.emptySet(); } @@ -500,4 +589,13 @@ private static T getOnlyElementInternal(@Nullable Iterable input, boolean throw new SafeIllegalArgumentException("Expected one element", SafeArg.of("size", size)); } } + + /** Throws a SafeIllegalArgumentException rather than NPE in order to cause a 400 response. */ + @CanIgnoreReturnValue + private static T checkArgumentNotNull(@Nullable T input) { + if (input == null) { + throw new SafeIllegalArgumentException("Value is required"); + } + return input; + } } diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureSerializerRegistry.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureSerializerRegistry.java deleted file mode 100644 index 01f78c3e1..000000000 --- a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureSerializerRegistry.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.runtime; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ObjectArrays; -import com.google.common.reflect.TypeToken; -import com.palantir.conjure.java.undertow.lib.SerializerRegistry; -import com.palantir.logsafe.Preconditions; -import com.palantir.logsafe.SafeArg; -import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.HeaderValues; -import io.undertow.util.Headers; -import java.io.IOException; - -/** Orchestrates serialization and deserialization of response and request bodies. */ -public final class ConjureSerializerRegistry implements SerializerRegistry { - - private final Serializer defaultSerializer; - private final Serializer[] serializers; - - /** - * Creates a registry that {@link #getResponseSerializer selects} the first (based on input order) of the - * provided serializers that {@link Serializer#supportsContentType supports} the serialization format - * {@link Headers#ACCEPT accepted} by a given request, or the first serializer if no such serializer can be found. - */ - public ConjureSerializerRegistry(Serializer defaultSerializer, Serializer... serializers) { - this.defaultSerializer = defaultSerializer; - this.serializers = ObjectArrays.concat(defaultSerializer, serializers); - } - - /** - * Provides a default configuration of the {@link SerializerRegistry}. - */ - public static SerializerRegistry getDefault() { - return new ConjureSerializerRegistry(Serializers.json(), Serializers.cbor()); - } - - /** Returns the {@link Serializer} to use to deserialize the request body. */ - @VisibleForTesting - Serializer getRequestDeserializer(HttpServerExchange exchange) { - String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); - if (contentType == null) { - throw new SafeIllegalArgumentException("Request is missing Content-Type header"); - } - for (Serializer serializer : serializers) { - if (serializer.supportsContentType(contentType)) { - return serializer; - } - } - throw FrameworkException.unsupportedMediaType("Unsupported Content-Type", - SafeArg.of("Content-Type", contentType)); - } - - /** Returns the {@link Serializer} to use for the exchange response. */ - @VisibleForTesting - Serializer getResponseSerializer(HttpServerExchange exchange) { - HeaderValues acceptValues = exchange.getRequestHeaders().get(Headers.ACCEPT); - if (acceptValues != null) { - // This implementation prefers the client "Accept" order - for (String acceptValue : acceptValues) { - for (Serializer serializer : serializers) { - if (serializer.supportsContentType(acceptValue)) { - return serializer; - } - } - } - } - // Fall back to the default - return defaultSerializer; - } - - /** Serialize a value to a provided exchange. */ - @Override - public void serialize(Object value, HttpServerExchange exchange) throws IOException { - Preconditions.checkNotNull(value, "cannot serialize null value"); - Serializer serializer = getResponseSerializer(exchange); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, serializer.getContentType()); - serializer.serialize(value, exchange.getOutputStream()); - } - - @Override - public T deserialize(TypeToken type, HttpServerExchange exchange) throws IOException { - Serializer serializer = getRequestDeserializer(exchange); - return serializer.deserialize(exchange.getInputStream(), type); - } -} diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureUndertowRuntime.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureUndertowRuntime.java new file mode 100644 index 000000000..f9670609c --- /dev/null +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/ConjureUndertowRuntime.java @@ -0,0 +1,78 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.runtime; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.palantir.conjure.java.undertow.lib.AuthorizationExtractor; +import com.palantir.conjure.java.undertow.lib.BodySerDe; +import com.palantir.conjure.java.undertow.lib.PlainSerDe; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.logsafe.Preconditions; +import java.util.List; + +/** + * {@link ConjureUndertowRuntime} provides functionality required by generated handlers. + */ +public final class ConjureUndertowRuntime implements UndertowRuntime { + + private final BodySerDe bodySerDe; + private final AuthorizationExtractor auth; + + private ConjureUndertowRuntime(Builder builder) { + this.bodySerDe = new ConjureBodySerDe(builder.encodings.isEmpty() + ? ImmutableList.of(Encodings.json(), Encodings.cbor()) : builder.encodings); + this.auth = new ConjureAuthorizationExtractor(plainSerDe()); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public BodySerDe bodySerDe() { + return bodySerDe; + } + + @Override + public PlainSerDe plainSerDe() { + return ConjurePlainSerDe.INSTANCE; + } + + @Override + public AuthorizationExtractor auth() { + return auth; + } + + public static final class Builder { + + private final List encodings = Lists.newArrayList(); + + private Builder() {} + + @CanIgnoreReturnValue + public Builder encodings(Encoding value) { + encodings.add(Preconditions.checkNotNull(value, "Value is required")); + return this; + } + + public ConjureUndertowRuntime build() { + return new ConjureUndertowRuntime(this); + } + } +} diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Encoding.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Encoding.java new file mode 100644 index 000000000..fca806d10 --- /dev/null +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Encoding.java @@ -0,0 +1,78 @@ +/* + * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.runtime; + +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An encoding provides support for a
Content-Type
corresponding with the conjure wire format. Encodings + * provide a {@link Encoding#getContentType() content type} string as well as factories for typed {@link Serializer} + * and {@link Deserializer} objects. + * + * This interface is considered internal API and may change without a major version rev. Custom implementations + * are not recommended, but may be implemented to test custom encodings without a fork of the runtime. + */ +public interface Encoding { + + /** + * Creates a new {@link Serializer} for the requested type. It is recommended to reuse instances over requesting + * new ones for each request. + */ + Serializer serializer(TypeMarker type); + + /** + * Creates a new {@link Deserializer} for the requested type. It is recommended to reuse instances over requesting + * new ones for each request. + */ + Deserializer deserializer(TypeMarker type); + + /** Returns the value used in response
Content-Type
header. */ + String getContentType(); + + /** + * Checks if a
Content-Type
or
Accept
value is supported by this encoding. This is not an + * exact match on {@link #getContentType()} because values may contain additional metadata, for + * example
Content-Type: application/json; charset=utf-8
may be supported by an {@link Encoding} + * which returns
application/json
from {@link #getContentType()}. + */ + boolean supportsContentType(String contentType); + + interface Deserializer { + + /** + * Reads a serialized type-{@link T} object representation from the given input stream and returns the + * corresponding object. Implementations should read the entire input stream, but must not close it. + * Format-related deserialization errors surface as {@link IllegalArgumentException}. Inputs and outputs + * must never be null. + */ + T deserialize(InputStream input) throws IOException; + + } + + interface Serializer { + + /** + * Serializes the given object and writes the serialized representation to the given output stream. + * Implementations must not close the stream. Inputs must never be null. + */ + void serialize(T value, OutputStream output) throws IOException; + + } +} diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Serializers.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Encodings.java similarity index 60% rename from conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Serializers.java rename to conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Encodings.java index 618b21b9a..6c3336851 100644 --- a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Serializers.java +++ b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Encodings.java @@ -18,58 +18,66 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.google.common.reflect.TypeToken; import com.palantir.conjure.java.serialization.ObjectMappers; +import com.palantir.conjure.java.undertow.lib.TypeMarker; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIoException; import java.io.FilterOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; // TODO(rfink): Consider async Jackson, see // https://github.com/spring-projects/spring-framework/commit/31e0e537500c0763a36d3af2570d5c253a374690 // and https://groups.google.com/forum/#!topic/jackson-user/m_prSo8d_Pw -public final class Serializers { +public final class Encodings { - private Serializers() {} + private Encodings() {} - private abstract static class AbstractJacksonSerializer implements Serializer { + private abstract static class AbstractJacksonEncoding implements Encoding { private final ObjectMapper mapper; - AbstractJacksonSerializer(ObjectMapper mapper) { + AbstractJacksonEncoding(ObjectMapper mapper) { this.mapper = Preconditions.checkNotNull(mapper, "ObjectMapper is required"); } @Override - public void serialize(Object value, OutputStream output) throws IOException { - Preconditions.checkNotNull(value, "cannot serialize null value"); - mapper.writeValue(output, value); + public Serializer serializer(TypeMarker type) { + ObjectWriter writer = mapper.writerFor(mapper.constructType(type.getType())); + return (value, output) -> { + Preconditions.checkNotNull(value, "cannot serialize null value"); + writer.writeValue(output, value); + }; } @Override - public final T deserialize(InputStream input, TypeToken type) throws IOException { - try { - T value = mapper.readValue(input, mapper.constructType(type.getType())); - // Bad input should result in a 4XX response status, throw IAE rather than NPE. - Preconditions.checkArgument(value != null, "cannot deserialize a JSON null value"); - return value; - } catch (MismatchedInputException e) { - throw FrameworkException.unprocessableEntity("Failed to deserialize response stream. Syntax error?", - e, SafeArg.of("type", type.getType())); - } catch (IOException e) { - throw new SafeIoException( - "Failed to deserialize response stream", e, SafeArg.of("type", type.getType())); - } + public Deserializer deserializer(TypeMarker type) { + ObjectReader reader = mapper.readerFor(mapper.constructType(type.getType())); + return input -> { + try { + T value = reader.readValue(input); + // Bad input should result in a 4XX response status, throw IAE rather than NPE. + Preconditions.checkArgument(value != null, "cannot deserialize a JSON null value"); + return value; + } catch (MismatchedInputException e) { + throw FrameworkException.unprocessableEntity( + "Failed to deserialize response stream. Syntax error?", + e, SafeArg.of("type", type.getType())); + } catch (IOException e) { + throw new SafeIoException( + "Failed to deserialize response stream", e, SafeArg.of("type", type.getType())); + } + }; } } /** Returns a serializer for the Conjure JSON wire format. */ - public static Serializer json() { - return new AbstractJacksonSerializer(configure(ObjectMappers.newServerObjectMapper())) { + public static Encoding json() { + return new AbstractJacksonEncoding(configure(ObjectMappers.newServerObjectMapper())) { private static final String CONTENT_TYPE = "application/json"; @@ -89,8 +97,8 @@ public boolean supportsContentType(String contentType) { } /** Returns a serializer for the Conjure CBOR wire format. */ - public static Serializer cbor() { - return new AbstractJacksonSerializer(configure(ObjectMappers.newCborServerObjectMapper())) { + public static Encoding cbor() { + return new AbstractJacksonEncoding(configure(ObjectMappers.newCborServerObjectMapper())) { private static final String CONTENT_TYPE = "application/cbor"; @@ -105,14 +113,15 @@ public boolean supportsContentType(String contentType) { } @Override - public void serialize(Object value, OutputStream output) throws IOException { - super.serialize(value, new ShieldingOutputStream(output)); + public Serializer serializer(TypeMarker type) { + Serializer delegate = super.serializer(type); + return (value, output) -> delegate.serialize(value, new ShieldingOutputStream(output)); } }; } private static ObjectMapper configure(ObjectMapper mapper) { - // See documentation on Serializer#serialize: Implementations must not close the stream. + // See documentation on Encoding.Serializer#serialize: Implementations must not close the stream. return mapper.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET) // Avoid flushing, allowing us to set content-length if the length is below the buffer size. .disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM); diff --git a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Serializer.java b/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Serializer.java deleted file mode 100644 index 15b6f0119..000000000 --- a/conjure-java-undertow-runtime/src/main/java/com/palantir/conjure/java/undertow/runtime/Serializer.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.runtime; - -import com.google.common.reflect.TypeToken; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** Serializes and deserializes Java objects into the corresponding wire format. */ -public interface Serializer { - /** - * Serializes the given object and writes the serialized representation to the given output stream. - * Implementations must not close the stream. Inputs must never be null. - */ - void serialize(Object value, OutputStream output) throws IOException; - - /** - * Reads a serialized type-{@link T} object representation from the given input stream and returns the - * corresponding object. Implementations should read the entire input stream, but must not close it. - * Format-related deserialization errors surface as {@link IllegalArgumentException}. Inputs and outputs - * must never be null. - */ - T deserialize(InputStream input, TypeToken type) throws IOException; - - String getContentType(); - - boolean supportsContentType(String contentType); -} diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/HttpServerExchanges.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/HttpServerExchanges.java index b9f5069b7..f9e67aba8 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/HttpServerExchanges.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/HttpServerExchanges.java @@ -56,6 +56,7 @@ private static HttpServerExchange createExchange(ServerConnection connection) { HttpServerExchange httpServerExchange = new HttpServerExchange(connection, new HeaderMap(), new HeaderMap(), 200); httpServerExchange.setProtocol(Protocols.HTTP_1_1); + httpServerExchange.startBlocking(); return httpServerExchange; } } diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyService.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyService.java deleted file mode 100644 index 731984621..000000000 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.demo; - -import java.time.OffsetDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** Sample API as generated by conjure-undertow. */ -public interface MyService { - - /** - * Increments the given base date by the number of hours. - */ - OffsetDateTime incrementTime(OffsetDateTime base, int numHours); - - /** - * Returns a map indicating for each of the given dates whether they are a Sunday or not. - */ - Map isSunday(List dates); - - /** Returns the trace ID made available to the server implementation. */ - String returnTrace(); - - /** Returns a string iff {@code shouldReturnString == true}. */ - Optional maybeString(boolean shouldReturnString); -} diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceImpl.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceImpl.java deleted file mode 100644 index cf58cc855..000000000 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceImpl.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.demo; - -import com.palantir.conjure.java.api.errors.ErrorType; -import com.palantir.conjure.java.api.errors.ServiceException; -import com.palantir.logsafe.exceptions.SafeRuntimeException; -import com.palantir.tracing.Tracer; -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.OffsetDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -public final class MyServiceImpl implements MyService { - @Override - public OffsetDateTime incrementTime(OffsetDateTime base, int numHours) { - if (numHours < 0) { - throw new ServiceException(ErrorType.INVALID_ARGUMENT); - } else if (numHours == 0) { - throw new SafeRuntimeException("Must not add 0 hours"); - } - - return base.plus(Duration.ofHours(numHours)); - } - - @Override - public Map isSunday(List dates) { - return dates - .stream() - .collect(Collectors.toMap(d -> d, d -> d.getDayOfWeek().equals(DayOfWeek.SUNDAY))); - } - - @Override - public String returnTrace() { - return Tracer.getTraceId(); - } - - @Override - public Optional maybeString(boolean shouldReturnString) { - return shouldReturnString ? Optional.of("foo") : Optional.empty(); - } -} diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceUndertowHandler.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceUndertowHandler.java deleted file mode 100644 index ee9136027..000000000 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceUndertowHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.demo; - -import com.google.common.reflect.TypeToken; -import com.palantir.conjure.java.undertow.lib.Endpoint; -import com.palantir.conjure.java.undertow.lib.EndpointRegistry; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.lib.internal.StringDeserializers; -import com.palantir.conjure.java.undertow.runtime.Serializer; -import io.undertow.server.HttpHandler; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.PathTemplateMatch; -import io.undertow.util.StatusCodes; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** Handler as generated by conjure-undertow. */ -public final class MyServiceUndertowHandler implements Registrable { - - private final MyService delegate; - private final Serializer bodySerializer; - - public MyServiceUndertowHandler( - MyService delegate, - Serializer bodySerializer) { - this.delegate = delegate; - this.bodySerializer = bodySerializer; - } - - @Override - public void register(EndpointRegistry endpointRegistry) { - endpointRegistry - .add(Endpoint.get("/inc/{base}/{numHours}"), new IncrementTimeHandler()) - .add(Endpoint.post("/issunday"), new IsSundayHandler()) - .add(Endpoint.get("/traceId"), new TraceIdHandler()) - .add(Endpoint.get("/maybeString/{shouldReturnString}"), new MaybeStringHandler()); - - } - - // TODO(nmiyake): If we decide to go generated handler route, rewrite all of this example code - private class IncrementTimeHandler implements HttpHandler { - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - Map params = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - OffsetDateTime base = StringDeserializers.deserializeDateTime(params.get("base")); - int numHours = StringDeserializers.deserializeInteger(params.get("numHours")); - - OffsetDateTime result = delegate.incrementTime(base, numHours); - bodySerializer.serialize(result, exchange.getOutputStream()); - } - } - - private class IsSundayHandler implements HttpHandler { - - private final TypeToken> datesType = new TypeToken>() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - List dates = bodySerializer.deserialize(exchange.getInputStream(), datesType); - - Map result = delegate.isSunday(dates); - bodySerializer.serialize(result, exchange.getOutputStream()); - } - } - - private class TraceIdHandler implements HttpHandler { - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - String result = delegate.returnTrace(); - bodySerializer.serialize(result, exchange.getOutputStream()); - } - } - - private class MaybeStringHandler implements HttpHandler { - - // private final TypeToken shouldReturnStringType = new TypeToken() {}; - - @Override - public void handleRequest(HttpServerExchange exchange) throws IOException { - Map params = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY).getParameters(); - // boolean shouldReturnString = - // plainSerializer.deserialize(params.get("shouldReturnString"), shouldReturnStringType); - boolean shouldReturnString = StringDeserializers.deserializeBoolean(params.get("shouldReturnString")); - Optional result = delegate.maybeString(shouldReturnString); - if (result.isPresent()) { - bodySerializer.serialize(result, exchange.getOutputStream()); - } else { - exchange.setStatusCode(StatusCodes.NO_CONTENT); - } - } - } -} diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceUndertowHandlerTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceUndertowHandlerTest.java deleted file mode 100644 index 73b2c64ac..000000000 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/demo/MyServiceUndertowHandlerTest.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.demo; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.common.collect.ImmutableList; -import com.google.common.reflect.TypeToken; -import com.palantir.conjure.java.undertow.lib.Registrable; -import com.palantir.conjure.java.undertow.runtime.ConjureHandler; -import com.palantir.conjure.java.undertow.runtime.Serializer; -import com.palantir.conjure.java.undertow.runtime.Serializers; -import com.palantir.logsafe.exceptions.SafeRuntimeException; -import com.palantir.tracing.Tracers; -import com.palantir.tracing.api.TraceHttpHeaders; -import io.undertow.Undertow; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.util.Map; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.BufferedSink; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -// TODO(rfink): Run these tests against the code produced by the conjure generator -@RunWith(MockitoJUnitRunner.class) -public final class MyServiceUndertowHandlerTest { - - private static final OkHttpClient client = new OkHttpClient.Builder().build(); - private final Serializer serializer = Serializers.json(); - - private Undertow server; - - @Before - public void before() { - Registrable registrable = new MyServiceUndertowHandler( - new MyServiceImpl(), - serializer - ); - ConjureHandler handler = new ConjureHandler(); - registrable.register(handler); - server = Undertow.builder() - .addHttpListener(12345, "localhost") - .setHandler(handler) - .build(); - server.start(); - } - - @After - public void after() { - server.stop(); - } - - @Test - public void incrementTime_sanity() throws IOException { - OffsetDateTime now = OffsetDateTime.now(); - int numIncHours = 2; - - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/inc/" + now + "/" + numIncHours) - .build(); - - OffsetDateTime result = serializer.deserialize( - execute(request).byteStream(), new TypeToken() {}); - assertThat(result).isEqualTo(now.plusHours(numIncHours)); - } - - @Test - public void isSunday_sanity() throws IOException { - OffsetDateTime wednesday = OffsetDateTime.parse("2018-08-15T13:00:00.000+00:00"); - OffsetDateTime sunday = OffsetDateTime.parse("2018-08-19T13:00:00.000+00:00"); - - Request request = new Request.Builder() - .post(createBody(ImmutableList.of(wednesday, sunday))) - .url("http://localhost:12345/issunday") - .build(); - - Map result = serializer.deserialize( - execute(request).byteStream(), new TypeToken>() {}); - assertThat(result) - .containsEntry(wednesday, false) - .containsEntry(sunday, true); - } - - @Test - public void handlesServiceExceptions() throws IOException { - // Setup a request that throws a ServiceException (see MyServiceImpl) - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/inc/" + OffsetDateTime.now() + "/-1") // ServiceException(INVALID_ARG) - .build(); - - try (Response response = client.newCall(request).execute()) { - assertThat(response.code()).isEqualTo(400); - } - } - - @Test - public void handlesSerializationErrors() throws IOException { - // Setup a request that throws a ServiceException (see MyServiceImpl) - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/inc/" + OffsetDateTime.now() - + "_BOGUS/2") // ServiceException(INVALID_ARG) - .build(); - - try (Response response = client.newCall(request).execute()) { - assertThat(response.code()).isEqualTo(400); - } - } - - @Test - public void handlesNonServiceExceptions() throws IOException { - // Setup a request that throws a RuntimeException (see MyServiceImpl) - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/inc/" + OffsetDateTime.now() - + "/0") // RuntimeException - .build(); - - try (Response response = client.newCall(request).execute()) { - assertThat(response.code()).isEqualTo(500); - } - } - - @Test - public void handlesTraceIdPropagation() throws IOException { - String traceId = Tracers.randomId(); - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/traceId") - .header(TraceHttpHeaders.TRACE_ID, traceId) - .build(); - try (Response response = client.newCall(request).execute()) { - assertThat(serializer.deserialize( - response.body().byteStream(), new TypeToken() {})).isEqualTo(traceId); - } - } - - @Test - public void handlesNotFound() throws IOException { - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/unmatched") - .build(); - try (Response response = client.newCall(request).execute()) { - // Do we expect a service exception here? - assertThat(response.code()).isEqualTo(404); - } - } - - @Test - public void handlesOptionalReturnValues() throws IOException { - // Optional.of("foo") --> body with "foo" and response code 200. - Request request = new Request.Builder() - .get() - .url("http://localhost:12345/maybeString/true") - .build(); - try (Response response = client.newCall(request).execute()) { - assertThat(response.code()).isEqualTo(200); - assertThat(serializer.deserialize( - response.body().byteStream(), new TypeToken() {})).isEqualTo("foo"); - } - - // Optional.absent() --> empty body and response code 204. - request = new Request.Builder() - .get() - .url("http://localhost:12345/maybeString/false") - .build(); - try (Response response = client.newCall(request).execute()) { - assertThat(response.code()).isEqualTo(204); - assertThat(response.body().contentLength()).isEqualTo(0); - } - } - - private RequestBody createBody(Object value) { - return new RequestBody() { - @Override - public MediaType contentType() { - return MediaType.parse("application/json"); - } - - @Override - public void writeTo(BufferedSink sink) { - try { - serializer.serialize(value, sink.outputStream()); - } catch (IOException e) { - throw new SafeRuntimeException(e); - } - } - }; - } - - private static ResponseBody execute(Request request) { - try { - Response response = client.newCall(request).execute(); - assertThat(response.code()).isEqualTo(200); - return response.body(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/SerializableErrorTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/SerializableErrorTest.java index a875158f4..316933bbf 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/SerializableErrorTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/SerializableErrorTest.java @@ -21,7 +21,7 @@ import com.palantir.conjure.java.api.errors.ErrorType; import com.palantir.conjure.java.api.errors.SerializableError; import com.palantir.conjure.java.api.errors.ServiceException; -import com.palantir.conjure.java.undertow.runtime.Serializers; +import com.palantir.conjure.java.undertow.runtime.Encodings; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.UnsafeArg; import java.io.ByteArrayOutputStream; @@ -35,7 +35,7 @@ public void serializationSanity() throws IOException { SerializableError error = SerializableError.forException( new ServiceException(ErrorType.INVALID_ARGUMENT, SafeArg.of("foo", 42), UnsafeArg.of("bar", "boom"))); ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Serializers.json().serialize(error, stream); + Encodings.json().serializer(new TypeMarker() {}).serialize(error, stream); assertThat(stream.toString()).isEqualTo( "{\"errorCode\":\"INVALID_ARGUMENT\",\"errorName\":\"Default:InvalidArgument\",\"errorInstanceId\":\"" + error.errorInstanceId() + "\",\"parameters\":{\"foo\":\"42\",\"bar\":\"boom\"}}"); diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/internal/AuthTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/AuthTest.java similarity index 81% rename from conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/internal/AuthTest.java rename to conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/AuthTest.java index 32ffa0b25..63bd2f86c 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/internal/AuthTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/AuthTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,13 @@ * limitations under the License. */ -package com.palantir.conjure.java.undertow.lib.internal; +package com.palantir.conjure.java.undertow.runtime; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.palantir.conjure.java.undertow.HttpServerExchanges; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; import com.palantir.tokens.auth.AuthHeader; import com.palantir.tokens.auth.BearerToken; @@ -30,18 +31,20 @@ public final class AuthTest { + private static final UndertowRuntime CONTEXT = ConjureUndertowRuntime.builder().build(); + @Test public void testParseAuthHeader() { AuthHeader expected = AuthHeader.of(BearerToken.valueOf("token")); HttpServerExchange exchange = HttpServerExchanges.createStub(); exchange.getRequestHeaders().add(Headers.AUTHORIZATION, expected.toString()); - assertThat(Auth.header(exchange)).isEqualTo(expected); + assertThat(CONTEXT.auth().header(exchange)).isEqualTo(expected); } @Test public void testAuthHeaderNotPresent() { HttpServerExchange exchange = HttpServerExchanges.createStub(); - assertThatThrownBy(() -> Auth.header(exchange)) + assertThatThrownBy(() -> CONTEXT.auth().header(exchange)) .isInstanceOf(SafeIllegalArgumentException.class) .hasMessage("One Authorization header value is required"); } @@ -51,7 +54,7 @@ public void testAuthHeaderMultipleValues() { HttpServerExchange exchange = HttpServerExchanges.createStub(); exchange.getRequestHeaders().add(Headers.AUTHORIZATION, "Bearer foo"); exchange.getRequestHeaders().add(Headers.AUTHORIZATION, "Bearer bar"); - assertThatThrownBy(() -> Auth.header(exchange)) + assertThatThrownBy(() -> CONTEXT.auth().header(exchange)) .isInstanceOf(SafeIllegalArgumentException.class) .hasMessage("One Authorization header value is required"); } diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/BearerTokenLoggingTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/BearerTokenLoggingTest.java index 7ff6d57e9..bc7d71700 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/BearerTokenLoggingTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/BearerTokenLoggingTest.java @@ -19,8 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.palantir.conjure.java.undertow.HttpServerExchanges; -import com.palantir.conjure.java.undertow.lib.Attachments; -import com.palantir.conjure.java.undertow.lib.internal.Auth; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.CookieImpl; @@ -60,6 +59,8 @@ public class BearerTokenLoggingTest { private static final String SESSION_ID = "3fc663d4-3e48-4ded-ba4e-d78af98b8363"; private static final String TOKEN_ID = "a459b4a1-5089-4fe0-8655-d5dfd9b2b7fd"; + private static final UndertowRuntime CONTEXT = ConjureUndertowRuntime.builder().build(); + private AtomicReference delegateRunnable = new AtomicReference<>(); private HttpHandler delegate = request -> delegateRunnable.get().run(); private HttpServerExchange exchange = HttpServerExchanges.createStub(); @@ -73,7 +74,7 @@ public void before() { exchange = HttpServerExchanges.createStub(); exchange.setRequestMethod(Methods.GET); handler = new LoggingContextHandler(httpServerExchange -> { - Auth.header(httpServerExchange); + CONTEXT.auth().header(httpServerExchange); delegate.handleRequest(httpServerExchange); }); } @@ -102,7 +103,7 @@ public void testSessionToken() throws Exception { @Test public void testCookieAuth() throws Exception { handler = new LoggingContextHandler(httpServerExchange -> { - Auth.cookie(httpServerExchange, "PALANTIR_TOKEN"); + CONTEXT.auth().cookie(httpServerExchange, "PALANTIR_TOKEN"); assertThat(MDC.get("userId")).isEqualTo(USER_ID); assertThat(MDC.get("sessionId")).isEqualTo(SESSION_ID); assertThat(MDC.get("tokenId")).isNull(); diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureBodySerDeTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureBodySerDeTest.java new file mode 100644 index 000000000..2df1028e1 --- /dev/null +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureBodySerDeTest.java @@ -0,0 +1,143 @@ +/* + * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.runtime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.google.common.collect.ImmutableList; +import com.palantir.conjure.java.undertow.HttpServerExchanges; +import com.palantir.conjure.java.undertow.lib.BodySerDe; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.logsafe.Preconditions; +import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import java.io.IOException; +import org.junit.Test; + +public class ConjureBodySerDeTest { + + private static final TypeMarker TYPE = new TypeMarker() {}; + + @Test + public void testRequestContentType() throws IOException { + Encoding json = new StubEncoding("application/json"); + Encoding plain = new StubEncoding("text/plain"); + + HttpServerExchange exchange = HttpServerExchanges.createStub(); + exchange.getRequestHeaders().put(Headers.CONTENT_TYPE, "text/plain"); + BodySerDe serializers = new ConjureBodySerDe(ImmutableList.of(json, plain)); + String value = serializers.deserializer(TYPE).deserialize(exchange); + assertThat(value).isEqualTo(plain.getContentType()); + } + + @Test + public void testRequestNoContentType() { + HttpServerExchange exchange = HttpServerExchanges.createStub(); + BodySerDe serializers = new ConjureBodySerDe(ImmutableList.of(new StubEncoding("application/json"))); + assertThatThrownBy(() -> serializers.deserializer(TYPE).deserialize(exchange)) + .isInstanceOf(SafeIllegalArgumentException.class) + .hasMessageContaining("Request is missing Content-Type header"); + } + + @Test + public void testUnsupportedRequestContentType() { + HttpServerExchange exchange = HttpServerExchanges.createStub(); + exchange.getRequestHeaders().put(Headers.CONTENT_TYPE, "application/unknown"); + BodySerDe serializers = new ConjureBodySerDe(ImmutableList.of(new StubEncoding("application/json"))); + assertThatThrownBy(() -> serializers.deserializer(TYPE).deserialize(exchange)) + .isInstanceOf(FrameworkException.class) + .hasMessageContaining("Unsupported Content-Type"); + } + + @Test + public void testResponseContentType() throws IOException { + Encoding json = new StubEncoding("application/json"); + Encoding plain = new StubEncoding("text/plain"); + + HttpServerExchange exchange = HttpServerExchanges.createStub(); + exchange.getRequestHeaders().put(Headers.ACCEPT, "text/plain"); + BodySerDe serializers = new ConjureBodySerDe(ImmutableList.of(json, plain)); + serializers.serializer(TYPE).serialize("test", exchange); + assertThat(exchange.getResponseHeaders().getFirst(Headers.CONTENT_TYPE)).isSameAs(plain.getContentType()); + } + + @Test + public void testResponseNoContentType() throws IOException { + Encoding json = new StubEncoding("application/json"); + Encoding plain = new StubEncoding("text/plain"); + + HttpServerExchange exchange = HttpServerExchanges.createStub(); + BodySerDe serializers = new ConjureBodySerDe(ImmutableList.of(json, plain)); + serializers.serializer(TYPE).serialize("test", exchange); + assertThat(exchange.getResponseHeaders().getFirst(Headers.CONTENT_TYPE)).isEqualTo(json.getContentType()); + } + + @Test + public void testResponseUnknownContentType() throws IOException { + Encoding json = new StubEncoding("application/json"); + Encoding plain = new StubEncoding("text/plain"); + + HttpServerExchange exchange = HttpServerExchanges.createStub(); + exchange.getRequestHeaders().put(Headers.ACCEPT, "application/unknown"); + BodySerDe serializers = new ConjureBodySerDe(ImmutableList.of(json, plain)); + serializers.serializer(TYPE).serialize("test", exchange); + assertThat(exchange.getResponseHeaders().getFirst(Headers.CONTENT_TYPE)).isEqualTo(json.getContentType()); + } + + /** Deserializes requests as the configured content type. */ + public static final class StubEncoding implements Encoding { + + private final String contentType; + + StubEncoding(String contentType) { + this.contentType = contentType; + } + + @Override + public Serializer serializer(TypeMarker type) { + return (value, output) -> { + // nop + }; + } + + @Override + @SuppressWarnings("unchecked") + public Deserializer deserializer(TypeMarker type) { + return input -> { + Preconditions.checkArgument(TYPE.equals(type), "This stub encoding only supports String"); + return (T) getContentType(); + }; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean supportsContentType(String input) { + return contentType.equals(input); + } + + @Override + public String toString() { + return "StubEncoding{" + contentType + '}'; + } + } +} diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandlerTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandlerTest.java index 06e779670..155de5383 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandlerTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureExceptionHandlerTest.java @@ -24,6 +24,7 @@ import com.palantir.conjure.java.api.errors.RemoteException; import com.palantir.conjure.java.api.errors.SerializableError; import com.palantir.conjure.java.api.errors.ServiceException; +import com.palantir.conjure.java.undertow.lib.TypeMarker; import com.palantir.logsafe.SafeArg; import io.undertow.Undertow; import io.undertow.server.handlers.BlockingHandler; @@ -51,8 +52,7 @@ public final class ConjureExceptionHandlerTest { public void before() { server = Undertow.builder() .addHttpListener(12345, "localhost") - .setHandler(new BlockingHandler(new ConjureExceptionHandler( - new ConjureSerializerRegistry(Serializers.json()), exchange -> { + .setHandler(new BlockingHandler(new ConjureExceptionHandler(exchange -> { throw exception; }))) .build(); @@ -89,7 +89,7 @@ public void handlesRemoteException() throws IOException { .errorInstanceId(remoteError.errorInstanceId()) .build(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Serializers.json().serialize(expectedPropagatedError, stream); + Encodings.json().serializer(new TypeMarker() {}).serialize(expectedPropagatedError, stream); assertThat(response.body().string()).isEqualTo(stream.toString()); // remote exceptions should result in 500 status @@ -161,8 +161,7 @@ public void doesNotHandleErrors() throws IOException { server.stop(); server = Undertow.builder() .addHttpListener(12345, "localhost") - .setHandler(new BlockingHandler(new ConjureExceptionHandler( - new ConjureSerializerRegistry(Serializers.json()), exchange -> { + .setHandler(new BlockingHandler(new ConjureExceptionHandler(exchange -> { throw new Error(); }))) .build(); diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureHandlerTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureHandlerTest.java index be0a6e401..c9c368ec1 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureHandlerTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/ConjureHandlerTest.java @@ -23,6 +23,8 @@ import com.palantir.conjure.java.undertow.lib.Endpoint; import io.undertow.Undertow; import io.undertow.server.HttpHandler; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; import java.io.IOException; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -52,14 +54,38 @@ interface Controller { @Before public void before() { - HttpHandler httpHandler = exchange -> { if (wrapperObserver.control() > 0) { innerObserver.control(); } }; - ConjureHandler handler = new ConjureHandler(); - handler.add(Endpoint.get("/test"), httpHandler); + HttpHandler handler = ConjureHandler.builder() + .endpoints(new Endpoint() { + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/test"; + } + + @Override + public HttpHandler handler() { + return httpHandler; + } + + @Override + public String serviceName() { + return "TestService"; + } + + @Override + public String name() { + return "test"; + } + }).build(); server = Undertow.builder() .addHttpListener(12345, "localhost") .setHandler(handler) diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/SerializersTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/EncodingsTest.java similarity index 72% rename from conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/SerializersTest.java rename to conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/EncodingsTest.java index 23620dd6d..4408d2790 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/SerializersTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/EncodingsTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import com.google.common.reflect.TypeToken; +import com.palantir.conjure.java.undertow.lib.TypeMarker; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; import com.palantir.logsafe.exceptions.SafeNullPointerException; import java.io.ByteArrayInputStream; @@ -34,15 +34,15 @@ import java.util.Optional; import org.junit.Test; -public final class SerializersTest { +public final class EncodingsTest { - private final Serializer json = Serializers.json(); + private final Encoding json = Encodings.json(); // TODO(rfink): Wire tests for JSON serializer @Test public void json_deserialize_throwsDeserializationErrorsAsIllegalArgumentException() { - assertThatThrownBy(() -> json.deserialize(asStream("\"2018-08-bogus\""), new TypeToken() {})) + assertThatThrownBy(() -> deserialize(asStream("\"2018-08-bogus\""), new TypeMarker() {})) .isInstanceOf(FrameworkException.class) .hasMessageContaining("Failed to deserialize") .matches(exception -> ((FrameworkException) exception).getStatusCode() == 422, "Expected 422 status"); @@ -50,26 +50,34 @@ public void json_deserialize_throwsDeserializationErrorsAsIllegalArgumentExcepti @Test public void json_serialize_rejectsNulls() { - assertThatThrownBy(() -> json.serialize(null /* under test: null value throws */, null /* unused stream */)) + assertThatThrownBy(() -> serialize(null /* under test: null value throws */, null /* unused stream */)) .isInstanceOf(SafeNullPointerException.class); } @Test public void json_deserialize_rejectsNulls() throws IOException { // TODO(rfink): Do we need to test this for all primitive types? - assertThatThrownBy(() -> json.deserialize(asStream("null"), new TypeToken() {})) + assertThatThrownBy(() -> deserialize(asStream("null"), new TypeMarker() {})) .isInstanceOf(SafeIllegalArgumentException.class); - assertThat(json.deserialize(asStream("null"), new TypeToken>() {})).isEmpty(); + assertThat(deserialize(asStream("null"), new TypeMarker>() {})).isEmpty(); } @Test public void json_serialize_doesNotCloseOutputStream() throws IOException { OutputStream outputStream = mock(OutputStream.class); - json.serialize("test", outputStream); + serialize("test", outputStream); verify(outputStream, never()).close(); } private static InputStream asStream(String data) { return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); } + + private void serialize(Object object, OutputStream stream) throws IOException { + json.serializer(new TypeMarker() {}).serialize(object, stream); + } + + private T deserialize(InputStream stream, TypeMarker token) throws IOException { + return json.deserializer(token).deserialize(stream); + } } diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/internal/StringDeserializersTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/PlainSerDeTest.java similarity index 60% rename from conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/internal/StringDeserializersTest.java rename to conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/PlainSerDeTest.java index e81a6c438..128b21310 100644 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/lib/internal/StringDeserializersTest.java +++ b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/PlainSerDeTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,21 @@ * limitations under the License. */ -package com.palantir.conjure.java.undertow.lib.internal; +package com.palantir.conjure.java.undertow.runtime; import static com.palantir.logsafe.testing.Assertions.assertThatLoggableExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.conjure.java.undertow.lib.PlainSerDe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.UnsafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; import com.palantir.ri.ResourceIdentifier; import com.palantir.tokens.auth.BearerToken; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.time.OffsetDateTime; import java.util.Optional; import java.util.OptionalDouble; @@ -34,7 +37,9 @@ import java.util.function.Function; import org.junit.Test; -public final class StringDeserializersTest { +public final class PlainSerDeTest { + + private static final PlainSerDe PLAIN = ConjurePlainSerDe.INSTANCE; @Test public void testDeserializeBearerToken() throws Exception { @@ -53,12 +58,12 @@ public void testDeserializeDateTime() throws Exception { @Test public void testDeserializeDouble() throws Exception { - runDeserializerTest("Double", "1.234", 1.234, in -> OptionalDouble.of(in)); + runDeserializerTest("Double", "1.234", 1.234, OptionalDouble::of); } @Test public void testDeserializeInteger() throws Exception { - runDeserializerTest("Integer", "13", 13, in -> OptionalInt.of(in)); + runDeserializerTest("Integer", "13", 13, OptionalInt::of); } @Test @@ -86,7 +91,7 @@ public void testDeserializeUuid() throws Exception { @Test public void testBearerTokensNotIncludedInThrowable() { assertThatLoggableExceptionThrownBy(() -> - StringDeserializers.deserializeBearerToken(ImmutableList.of("one", "two", "three"))) + PLAIN.deserializeBearerToken(ImmutableList.of("one", "two", "three"))) .isInstanceOf(SafeIllegalArgumentException.class) .hasLogMessage("Expected one element") .hasExactlyArgs(SafeArg.of("size", 3)); @@ -95,7 +100,7 @@ public void testBearerTokensNotIncludedInThrowable() { @Test public void testValuesAreLoggedUnsafe() { assertThatLoggableExceptionThrownBy(() -> - StringDeserializers.deserializeString(ImmutableList.of("one", "two", "three"))) + PLAIN.deserializeString(ImmutableList.of("one", "two", "three"))) .isInstanceOf(SafeIllegalArgumentException.class) .hasLogMessage("Expected one element") .hasExactlyArgs(SafeArg.of("size", 3), @@ -103,22 +108,41 @@ public void testValuesAreLoggedUnsafe() { } private static void runDeserializerTest(String typeName, String plainIn, T want) throws Exception { - runDeserializerTest(typeName, plainIn, want, in -> Optional.of(in)); + runDeserializerTest(typeName, plainIn, want, Optional::of); } private static void runDeserializerTest(String typeName, String plainIn, T want, Function createOptional) throws Exception { - assertThat(StringDeserializers.class.getMethod("deserialize" + typeName, String.class).invoke(null, - plainIn)).isEqualTo(want); + assertThat(PlainSerDe.class.getMethod("deserialize" + typeName, String.class) + .invoke(PLAIN, plainIn)).isEqualTo(want); + + assertThatLoggableExceptionThrownBy(() -> { + try { + PlainSerDe.class.getMethod("deserialize" + typeName, String.class) + .invoke(PLAIN, new Object[] {null}); + } catch (InvocationTargetException ite) { + throw ite.getCause(); + } + }) + .describedAs("invoking a string deserializer with null should result in an IAE") + .isInstanceOf(SafeIllegalArgumentException.class) + .hasLogMessage("Value is required"); + + assertThat(PlainSerDe.class.getMethod("deserialize" + typeName, Iterable.class) + .invoke(PLAIN, ImmutableList.of(plainIn))).isEqualTo(want); - assertThat(StringDeserializers.class.getMethod("deserialize" + typeName, Iterable.class).invoke(null, - ImmutableList.of(plainIn))).isEqualTo(want); + Method optionalStringDeserializer = PlainSerDe.class.getMethod("deserializeOptional" + typeName, String.class); + assertThat(optionalStringDeserializer.invoke(PLAIN, plainIn)).isEqualTo(createOptional.apply(want)); - assertThat(StringDeserializers.class.getMethod("deserializeOptional" + typeName, String.class).invoke(null, - plainIn)).isEqualTo(createOptional.apply(want)); + assertThat(optionalStringDeserializer.invoke(PLAIN, new Object[] { null })) + .isEqualTo(createEmptyOptional(optionalStringDeserializer)); + + assertThat(PlainSerDe.class.getMethod("deserializeOptional" + typeName, Iterable.class) + .invoke(PLAIN, ImmutableList.of(plainIn))).isEqualTo(createOptional.apply(want)); + } - assertThat(StringDeserializers.class.getMethod("deserializeOptional" + typeName, Iterable.class).invoke(null, - ImmutableList.of(plainIn))).isEqualTo(createOptional.apply(want)); + private static Object createEmptyOptional(Method deserializerMethod) throws ReflectiveOperationException { + return deserializerMethod.getReturnType().getDeclaredMethod("empty").invoke(null); } } diff --git a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/SerializerRegistryTest.java b/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/SerializerRegistryTest.java deleted file mode 100644 index 3e01f6e85..000000000 --- a/conjure-java-undertow-runtime/src/test/java/com/palantir/conjure/java/undertow/runtime/SerializerRegistryTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.runtime; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.google.common.reflect.TypeToken; -import com.palantir.conjure.java.undertow.HttpServerExchanges; -import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.Headers; -import java.io.InputStream; -import java.io.OutputStream; -import org.junit.Test; - -public class SerializerRegistryTest { - - @Test - public void testRequestContentType() { - Serializer json = new StubSerializer("application/json"); - Serializer plain = new StubSerializer("text/plain"); - - HttpServerExchange exchange = HttpServerExchanges.createStub(); - exchange.getRequestHeaders().put(Headers.CONTENT_TYPE, "text/plain"); - ConjureSerializerRegistry serializers = new ConjureSerializerRegistry(json, plain); - Serializer serializer = serializers.getRequestDeserializer(exchange); - assertThat(serializer).isSameAs(plain); - } - - @Test - public void testRequestNoContentType() { - HttpServerExchange exchange = HttpServerExchanges.createStub(); - ConjureSerializerRegistry serializers = new ConjureSerializerRegistry(new StubSerializer("application/json")); - assertThatThrownBy(() -> serializers.getRequestDeserializer(exchange)) - .isInstanceOf(SafeIllegalArgumentException.class) - .hasMessageContaining("Request is missing Content-Type header"); - } - - @Test - public void testUnsupportedRequestContentType() { - HttpServerExchange exchange = HttpServerExchanges.createStub(); - exchange.getRequestHeaders().put(Headers.CONTENT_TYPE, "application/unknown"); - ConjureSerializerRegistry serializers = new ConjureSerializerRegistry(new StubSerializer("application/json")); - assertThatThrownBy(() -> serializers.getRequestDeserializer(exchange)) - .isInstanceOf(FrameworkException.class) - .hasMessageContaining("Unsupported Content-Type"); - } - - @Test - public void testResponseContentType() { - Serializer json = new StubSerializer("application/json"); - Serializer plain = new StubSerializer("text/plain"); - - HttpServerExchange exchange = HttpServerExchanges.createStub(); - exchange.getRequestHeaders().put(Headers.ACCEPT, "text/plain"); - ConjureSerializerRegistry serializers = new ConjureSerializerRegistry(json, plain); - Serializer serializer = serializers.getResponseSerializer(exchange); - assertThat(serializer).isSameAs(plain); - } - - @Test - public void testResponseNoContentType() { - Serializer json = new StubSerializer("application/json"); - Serializer plain = new StubSerializer("text/plain"); - - HttpServerExchange exchange = HttpServerExchanges.createStub(); - ConjureSerializerRegistry serializers = new ConjureSerializerRegistry(json, plain); - Serializer serializer = serializers.getResponseSerializer(exchange); - assertThat(serializer).isSameAs(json); - } - - @Test - public void testResponseUnknownContentType() { - Serializer json = new StubSerializer("application/json"); - Serializer plain = new StubSerializer("text/plain"); - - HttpServerExchange exchange = HttpServerExchanges.createStub(); - exchange.getRequestHeaders().put(Headers.ACCEPT, "application/unknown"); - ConjureSerializerRegistry serializers = new ConjureSerializerRegistry(json, plain); - Serializer serializer = serializers.getResponseSerializer(exchange); - assertThat(serializer).isSameAs(json); - } - - public static final class StubSerializer implements Serializer { - - private final String contentType; - - StubSerializer(String contentType) { - this.contentType = contentType; - } - - @Override - public void serialize(Object value, OutputStream output) { - throw new UnsupportedOperationException(); - } - - @Override - public T deserialize(InputStream input, TypeToken type) { - throw new UnsupportedOperationException(); - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public boolean supportsContentType(String input) { - return contentType.equals(input); - } - - @Override - public String toString() { - return "StubSerializer{" + contentType + '}'; - } - } -} diff --git a/conjure-undertow-lib/build.gradle b/conjure-undertow-lib/build.gradle index af28f8a43..bb6097957 100644 --- a/conjure-undertow-lib/build.gradle +++ b/conjure-undertow-lib/build.gradle @@ -22,8 +22,4 @@ dependencies { api 'io.undertow:undertow-core' // Generated code uses guava TypeToken api 'com.google.guava:guava' - implementation 'org.slf4j:slf4j-api' - - compileOnly 'org.immutables:value::annotations' - annotationProcessor 'org.immutables:value' } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/SerializerRegistry.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/AuthorizationExtractor.java similarity index 54% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/SerializerRegistry.java rename to conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/AuthorizationExtractor.java index 2b9702703..6b59d247d 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/SerializerRegistry.java +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/AuthorizationExtractor.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,17 @@ package com.palantir.conjure.java.undertow.lib; -import com.google.common.reflect.TypeToken; +import com.palantir.tokens.auth.AuthHeader; +import com.palantir.tokens.auth.BearerToken; import io.undertow.server.HttpServerExchange; -import java.io.IOException; -/** Orchestrates serialization and deserialization of response and request bodies. */ -public interface SerializerRegistry { +/** Provides auth functionality for generated code. */ +public interface AuthorizationExtractor { - /** Serialize a value to a provided exchange. */ - void serialize(Object value, HttpServerExchange exchange) throws IOException; + /** Parses an {@link AuthHeader} from the provided {@link HttpServerExchange}. */ + AuthHeader header(HttpServerExchange exchange); + + /** Parses a {@link BearerToken} from the provided {@link HttpServerExchange}. */ + BearerToken cookie(HttpServerExchange exchange, String cookieName); - /** Deserializes the request body into the requested type. */ - T deserialize(TypeToken type, HttpServerExchange exchange) throws IOException; } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/BodySerDe.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/BodySerDe.java new file mode 100644 index 000000000..78f4bb444 --- /dev/null +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/BodySerDe.java @@ -0,0 +1,42 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.lib; + +import io.undertow.server.HttpServerExchange; +import java.io.IOException; +import java.io.InputStream; + +/** Request and response Deserialization and Serialization functionality used by generated code. */ +public interface BodySerDe { + + /** Creates a {@link Serializer} for the requested type. Serializer instances should be reused. */ + Serializer serializer(TypeMarker type); + + /** Creates a {@link Deserializer} for the requested type. Deserializer instances should be reused. */ + Deserializer deserializer(TypeMarker type); + + /** Serializes a {@link BinaryResponseBody} to
application/octet-stream
. */ + void serialize(BinaryResponseBody value, HttpServerExchange exchange) throws IOException; + + /** + * Reads an {@link InputStream} from the {@link HttpServerExchange} request body. + * + * This method is named
deserializeInputStream
not
deserializeBinary
+ * to support future streaming binary bindings without conflicting method signatures. + */ + InputStream deserializeInputStream(HttpServerExchange exchange); +} diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/ServiceInstrumenter.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Deserializer.java similarity index 67% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/ServiceInstrumenter.java rename to conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Deserializer.java index 443d6d257..52125c826 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/ServiceInstrumenter.java +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Deserializer.java @@ -16,12 +16,13 @@ package com.palantir.conjure.java.undertow.lib; -/** - * {@link ServiceInstrumenter} may transform service implementations in order to apply instrumentation. - */ -public interface ServiceInstrumenter { +import io.undertow.server.HttpServerExchange; +import java.io.IOException; + +/** Reads objects from a request. */ +public interface Deserializer { - /** Applies instrumentation to the provided implementation, returning the instrumented service. */ - T instrument(T serviceImplementation, Class serviceInterface); + /** Deserialize the request body. */ + T deserialize(HttpServerExchange exchange) throws IOException; } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Endpoint.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Endpoint.java index 47d71c5da..b579dc9a1 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Endpoint.java +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Endpoint.java @@ -16,57 +16,34 @@ package com.palantir.conjure.java.undertow.lib; -import com.palantir.tokens.auth.ImmutablesStyle; +import io.undertow.server.HttpHandler; import io.undertow.util.HttpString; -import io.undertow.util.Methods; -import java.util.Optional; -import org.immutables.value.Value; -@Value.Immutable(builder = false) -@ImmutablesStyle +/** + * An {@link Endpoint} represents a single rpc method. End points provide a location, tuple of + * {@link Endpoint#method()} and {@link Endpoint#template()}, as well as an implementation, the + * {@link Endpoint#handler()}. + */ public interface Endpoint { - @Value.Parameter + /** HTTP method which matches this {@link Endpoint}. See {@link io.undertow.util.Methods}. */ HttpString method(); - @Value.Parameter + /** + * Conjure formatted http path template. + * For example, this may take the form
/ping
or
/object/{objectId}
. + * For more information, see the + * + * specification for conjure path strings. + */ String template(); - @Value.Parameter - Optional serviceName(); - - @Value.Parameter - Optional name(); - - static Endpoint get(String template) { - return ImmutableEndpoint.of(Methods.GET, template, Optional.empty(), Optional.empty()); - } - - static Endpoint get(String template, String serviceName, String name) { - return ImmutableEndpoint.of(Methods.GET, template, Optional.of(serviceName), Optional.of(name)); - } - - static Endpoint post(String template) { - return ImmutableEndpoint.of(Methods.POST, template, Optional.empty(), Optional.empty()); - } - - static Endpoint post(String template, String serviceName, String name) { - return ImmutableEndpoint.of(Methods.POST, template, Optional.of(serviceName), Optional.of(name)); - } - - static Endpoint put(String template) { - return ImmutableEndpoint.of(Methods.PUT, template, Optional.empty(), Optional.empty()); - } - - static Endpoint put(String template, String serviceName, String name) { - return ImmutableEndpoint.of(Methods.PUT, template, Optional.of(serviceName), Optional.of(name)); - } + /** Undertow {@link HttpHandler} which provides the endpoint implementation. */ + HttpHandler handler(); - static Endpoint delete(String template) { - return ImmutableEndpoint.of(Methods.DELETE, template, Optional.empty(), Optional.empty()); - } + /** Simple name of the service which provides this endpoint. This data may be used for metric instrumentation. */ + String serviceName(); - static Endpoint delete(String template, String serviceName, String name) { - return ImmutableEndpoint.of(Methods.DELETE, template, Optional.of(serviceName), Optional.of(name)); - } + /** Simple name of the endpoint method. This data may be used for metric instrumentation. */ + String name(); } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/EndpointRegistry.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/EndpointRegistry.java deleted file mode 100644 index 7ef09575e..000000000 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/EndpointRegistry.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.lib; - -import io.undertow.server.HttpHandler; - -/** - * Add handlers to this registry by calling the method corresponding to the http method - * with {@link HttpHandler} and the path template of the "route". - * The purpose of this layer of indirection on top of a {@link EndpointRegistry} is to restrict the exposed API - * and allow the usage of registered paths for other means than registering to a - * {@link io.undertow.server.RoutingHandler} (e.g" checking for overlapping paths) - */ -public interface EndpointRegistry { - - EndpointRegistry add(Endpoint description, HttpHandler handler); - -} diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/PlainSerDe.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/PlainSerDe.java new file mode 100644 index 000000000..1b3e6cd9d --- /dev/null +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/PlainSerDe.java @@ -0,0 +1,160 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.lib; + +import com.palantir.conjure.java.lib.SafeLong; +import com.palantir.ri.ResourceIdentifier; +import com.palantir.tokens.auth.BearerToken; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import javax.annotation.Nullable; + +/** + * Provides functionality to parse supported types using the + * Conjure PLAIN format. + * + * These utilities are used to parse HTTP path, query, and header parameter values. + */ +public interface PlainSerDe { + + // TODO(ckozak): Write javadoc + + BearerToken deserializeBearerToken(@Nullable String in); + + BearerToken deserializeBearerToken(@Nullable Iterable in); + + Optional deserializeOptionalBearerToken(@Nullable String in); + + Optional deserializeOptionalBearerToken(@Nullable Iterable in); + + List deserializeBearerTokenList(@Nullable Iterable in); + + Set deserializeBearerTokenSet(@Nullable Iterable in); + + boolean deserializeBoolean(@Nullable String in); + + boolean deserializeBoolean(@Nullable Iterable in); + + Optional deserializeOptionalBoolean(@Nullable String in); + + Optional deserializeOptionalBoolean(@Nullable Iterable in); + + List deserializeBooleanList(@Nullable Iterable in); + + Set deserializeBooleanSet(@Nullable Iterable in); + + OffsetDateTime deserializeDateTime(@Nullable String in); + + OffsetDateTime deserializeDateTime(@Nullable Iterable in); + + Optional deserializeOptionalDateTime(@Nullable String in); + + Optional deserializeOptionalDateTime(@Nullable Iterable in); + + List deserializeDateTimeList(@Nullable Iterable in); + + Set deserializeDateTimeSet(@Nullable Iterable in); + + double deserializeDouble(@Nullable String in); + + double deserializeDouble(@Nullable Iterable in); + + OptionalDouble deserializeOptionalDouble(@Nullable String in); + + OptionalDouble deserializeOptionalDouble(@Nullable Iterable in); + + List deserializeDoubleList(@Nullable Iterable in); + + Set deserializeDoubleSet(@Nullable Iterable in); + + int deserializeInteger(@Nullable String in); + + int deserializeInteger(@Nullable Iterable in); + + OptionalInt deserializeOptionalInteger(@Nullable String in); + + OptionalInt deserializeOptionalInteger(@Nullable Iterable in); + + List deserializeIntegerList(@Nullable Iterable in); + + Set deserializeIntegerSet(@Nullable Iterable in); + + ResourceIdentifier deserializeRid(String in); + + ResourceIdentifier deserializeRid(@Nullable Iterable in); + + Optional deserializeOptionalRid(@Nullable String in); + + Optional deserializeOptionalRid(@Nullable Iterable in); + + List deserializeRidList(@Nullable Iterable in); + + Set deserializeRidSet(@Nullable Iterable in); + + SafeLong deserializeSafeLong(@Nullable String in); + + SafeLong deserializeSafeLong(@Nullable Iterable in); + + Optional deserializeOptionalSafeLong(@Nullable String in); + + Optional deserializeOptionalSafeLong(@Nullable Iterable in); + + List deserializeSafeLongList(@Nullable Iterable in); + + Set deserializeSafeLongSet(@Nullable Iterable in); + + String deserializeString(@Nullable String in); + + String deserializeString(@Nullable Iterable in); + + Optional deserializeOptionalString(@Nullable String in); + + Optional deserializeOptionalString(@Nullable Iterable in); + + List deserializeStringList(@Nullable Iterable in); + + Set deserializeStringSet(@Nullable Iterable in); + + UUID deserializeUuid(@Nullable String in); + + UUID deserializeUuid(@Nullable Iterable in); + + Optional deserializeOptionalUuid(@Nullable String in); + + Optional deserializeOptionalUuid(@Nullable Iterable in); + + List deserializeUuidList(@Nullable Iterable in); + + Set deserializeUuidSet(@Nullable Iterable in); + + T deserializeComplex(@Nullable String in, Function factory); + + T deserializeComplex(@Nullable Iterable in, Function factory); + + Optional deserializeOptionalComplex(@Nullable Iterable in, Function factory); + + List deserializeComplexList(@Nullable Iterable in, Function factory); + + Set deserializeComplexSet(@Nullable Iterable in, Function factory); + +} diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Registrable.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Serializer.java similarity index 62% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Registrable.java rename to conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Serializer.java index 102f49845..a5d4bc1fe 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Registrable.java +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Serializer.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ package com.palantir.conjure.java.undertow.lib; -/** - * Used by {@link Service}s to create an object-thunk that can {@link #register} - * the service's pair of ({@link Endpoint}, {@link io.undertow.server.HttpHandler}) to - * {@link EndpointRegistry#add}. - */ -public interface Registrable { +import io.undertow.server.HttpServerExchange; +import java.io.IOException; + +/** Writes objects to a response. */ +public interface Serializer { - void register(EndpointRegistry endpointRegistry); + /** Serialize a value to a provided exchange using the Conjure wire format. */ + void serialize(T value, HttpServerExchange exchange) throws IOException; } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/ServiceContext.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/ServiceContext.java deleted file mode 100644 index 95aaa6c70..000000000 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/ServiceContext.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.lib; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.palantir.logsafe.Preconditions; - -/** - * {@link ServiceContext} provides state required by generated handlers. - */ -public final class ServiceContext { - - private final SerializerRegistry serializerRegistry; - private final ServiceInstrumenter serviceInstrumenter; - - private ServiceContext(Builder builder) { - this.serializerRegistry = Preconditions.checkNotNull(builder.serializerRegistry, - "Missing required SerializerRegistry"); - this.serviceInstrumenter = Preconditions.checkNotNull(builder.serviceInstrumenter, - "Missing required ServiceInstrumenter"); - } - - /** - * {@link SerializerRegistry} for request and response body serialization. - */ - public SerializerRegistry serializerRegistry() { - return serializerRegistry; - } - - /** - * {@link ServiceInstrumenter} to apply metric instrumentation to exposed services. - */ - public ServiceInstrumenter serviceInstrumenter() { - return serviceInstrumenter; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - - private SerializerRegistry serializerRegistry; - private ServiceInstrumenter serviceInstrumenter = new ServiceInstrumenter() { - @Override - public T instrument(T serviceImplementation, Class serviceInterface) { - return serviceImplementation; - } - }; - - private Builder() {} - - @CanIgnoreReturnValue - public Builder serializerRegistry(SerializerRegistry value) { - this.serializerRegistry = Preconditions.checkNotNull(value, "Value is required"); - return this; - } - - @CanIgnoreReturnValue - public Builder serviceInstrumenter(ServiceInstrumenter value) { - this.serviceInstrumenter = Preconditions.checkNotNull(value, "Value is required"); - return this; - } - - public ServiceContext build() { - return new ServiceContext(this); - } - } -} diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/TypeMarker.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/TypeMarker.java new file mode 100644 index 000000000..e1a778b9e --- /dev/null +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/TypeMarker.java @@ -0,0 +1,53 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.lib; + +import com.palantir.logsafe.Preconditions; +import com.palantir.logsafe.SafeArg; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +/** + * Captures generic type information. + * + * Usage example:
new TypeMarker<List<Integer>() {}
. + */ +@SuppressWarnings("unused") // Generic type exists for compile time safety but is not used internally. +public abstract class TypeMarker { + + private final Type type; + + protected TypeMarker() { + Type genericSuperclass = getClass().getGenericSuperclass(); + Preconditions.checkArgument(genericSuperclass instanceof ParameterizedType, + "Class is not parameterized", SafeArg.of("class", genericSuperclass)); + type = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; + Preconditions.checkArgument(!(type instanceof TypeVariable), + "TypeMarker does not support variable types", + SafeArg.of("typeVariable", type)); + } + + public final Type getType() { + return type; + } + + @Override + public final String toString() { + return "TypeMarker{type=" + type + '}'; + } +} diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/UndertowRuntime.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/UndertowRuntime.java new file mode 100644 index 000000000..9aad4ba3a --- /dev/null +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/UndertowRuntime.java @@ -0,0 +1,35 @@ +/* + * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.undertow.lib; + +/** + * {@link UndertowRuntime} is the anchor for all non-generated logic used by generated handlers. + * + * The {@link UndertowRuntime} and provided interfaces {@link BodySerDe}, {@link PlainSerDe}, and + * {@link AuthorizationExtractor} are internal API, no guarantees are made for custom implementations. + */ +public interface UndertowRuntime { + + /** Provides the {@link BodySerDe} used to deserialize and serialize request and response bodies respectively. */ + BodySerDe bodySerDe(); + + /** Provides the {@link PlainSerDe} used to parse request path, query, and header parameters. */ + PlainSerDe plainSerDe(); + + /** Provides the {@link AuthorizationExtractor} used to read auth tokens from request headers. */ + AuthorizationExtractor auth(); +} diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Service.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/UndertowService.java similarity index 56% rename from conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Service.java rename to conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/UndertowService.java index fbe268291..eb1d84e51 100644 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/Service.java +++ b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/UndertowService.java @@ -16,13 +16,17 @@ package com.palantir.conjure.java.undertow.lib; +import java.util.List; + /** - * Creates a {@link Registrable} which may be registered with a web server. The server is responsible - * for providing a {@link ServiceContext} allowing API implementors to add APIs using - * server.api(MyServiceEndpoints.of(myServiceImpl). + * The Undertow server Conjure generator emits implementations of {@link UndertowService} + * which produce an {@link Endpoint} for each endpoint described in the Conjure definition. + * The server is responsible for providing an {@link UndertowRuntime} and orchestrating + * registration with the
ConjureHandler
allowing API implementors to add APIs + * using server.api(MyServiceEndpoints.of(myServiceImpl). */ -public interface Service { +public interface UndertowService { - Registrable create(ServiceContext context); + List endpoints(UndertowRuntime runtime); } diff --git a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/BinarySerializers.java b/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/BinarySerializers.java deleted file mode 100644 index dd435c3be..000000000 --- a/conjure-undertow-lib/src/main/java/com/palantir/conjure/java/undertow/lib/internal/BinarySerializers.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.palantir.conjure.java.undertow.lib.internal; - -import com.palantir.conjure.java.undertow.lib.BinaryResponseBody; -import com.palantir.logsafe.Preconditions; -import com.palantir.logsafe.SafeArg; -import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.Headers; -import java.io.IOException; -import java.io.InputStream; - -public final class BinarySerializers { - - private static final String CONTENT_TYPE = "application/octet-stream"; - - public static void serialize(BinaryResponseBody value, HttpServerExchange exchange) throws IOException { - Preconditions.checkNotNull(value, "A BinaryResponseBody value is required"); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, CONTENT_TYPE); - value.write(exchange.getOutputStream()); - } - - public static InputStream deserializeInputStream(HttpServerExchange exchange) { - String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); - if (contentType == null) { - throw new SafeIllegalArgumentException("Request is missing Content-Type header"); - } - if (!contentType.startsWith(CONTENT_TYPE)) { - throw new SafeIllegalArgumentException("Unsupported Content-Type", - SafeArg.of("Content-Type", contentType)); - } - return exchange.getInputStream(); - } - - private BinarySerializers() {} -} diff --git a/readme.md b/readme.md index 7c1d14c31..94f7772c4 100644 --- a/readme.md +++ b/readme.md @@ -209,16 +209,12 @@ To use the generated handlers: ```java public static void main(String[] args) { - ConjureHandler handler = new ConjureHandler(); - RecipeBookServiceEndpoints.of(new RecipeBookResource()) - .create(ServerContext.builder() - .serializerRegistry(ConjureSerializerRegistry.getDefault()) - .build()) - .register(handler); - Undertow server = Undertow.builder() .addHttpListener(8080, "0.0.0.0") - .setHandler(handler) + .setHandler(ConjureHandler.builder() + .addAllEndpoints(RecipeBookServiceEndpoints.of(new RecipeBookResource()) + .endpoints(ConjureUndertowRuntime.builder().build())) + .build()) .build(); server.start(); }