From d0702bc00fd19d89d910ef817dcc62ec44b6341e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Feb 2021 14:02:24 +0200 Subject: [PATCH] Add Optional parameter support in RESTEasy Reactive This isn't part of the spec, but RESTEasy Classic has this feature (and we use it in the elastic search quickstart), so best add it --- .../deployment/ClientEndpointIndexer.java | 2 +- .../test/simple/NewParamsRestResource.java | 7 ++- .../simple/SimpleQuarkusRestTestCase.java | 3 +- .../common/processor/EndpointIndexer.java | 12 ++++ .../common/processor/IndexedParameter.java | 9 +++ .../common/model/MethodParameter.java | 12 +++- .../processor/ServerEndpointIndexer.java | 10 ++- .../converters/OptionalConverter.java | 63 +++++++++++++++++++ .../startup/RuntimeResourceDeployment.java | 2 +- .../server/handlers/ParameterHandler.java | 6 +- .../server/model/ServerMethodParameter.java | 6 +- 11 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java diff --git a/extensions/resteasy-reactive/quarkus-jaxrs-client/deployment/src/main/java/io/quarkus/resteasy/reactive/client/deployment/ClientEndpointIndexer.java b/extensions/resteasy-reactive/quarkus-jaxrs-client/deployment/src/main/java/io/quarkus/resteasy/reactive/client/deployment/ClientEndpointIndexer.java index aa44e5b6c8324..878a6b7f42333 100644 --- a/extensions/resteasy-reactive/quarkus-jaxrs-client/deployment/src/main/java/io/quarkus/resteasy/reactive/client/deployment/ClientEndpointIndexer.java +++ b/extensions/resteasy-reactive/quarkus-jaxrs-client/deployment/src/main/java/io/quarkus/resteasy/reactive/client/deployment/ClientEndpointIndexer.java @@ -83,7 +83,7 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas String elementType, boolean single, String signature) { return new MethodParameter(name, elementType, toClassName(paramType, currentClassInfo, actualEndpointInfo, index), signature, type, single, - defaultValue, parameterResult.isObtainedAsCollection(), encoded); + defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded); } protected void addWriterForType(AdditionalWriters additionalWriters, Type paramType) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/NewParamsRestResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/NewParamsRestResource.java index 6dd909f1e448b..c256197872b42 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/NewParamsRestResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/NewParamsRestResource.java @@ -1,5 +1,7 @@ package io.quarkus.resteasy.reactive.server.test.simple; +import java.util.Optional; + import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -42,11 +44,14 @@ public String get(String klass, String regex, String id) { @Path("params/{p}") public String params(@RestPath String p, @RestQuery String q, + @RestQuery Optional q2, + @RestQuery Optional q3, @RestHeader int h, @RestForm String f, @RestMatrix String m, @RestCookie String c) { - return "params: p: " + p + ", q: " + q + ", h: " + h + ", f: " + f + ", m: " + m + ", c: " + c; + return "params: p: " + p + ", q: " + q + ", h: " + h + ", f: " + f + ", m: " + m + ", c: " + c + ", q2: " + + q2.orElse("empty") + ", q3: " + q3.orElse(-1); } @GET diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java index 1e36826272f83..3e5f43bbe5fdc 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SimpleQuarkusRestTestCase.java @@ -379,12 +379,13 @@ public void testNewParams() { .urlEncodingEnabled(false) .cookie("c", "cv") .queryParam("q", "qv") + .queryParam("q3", "999") .header("h", "123") .formParam("f", "fv") .post("/new-params/myklass;m=mv/myregex/params/pv") .then() .log().ifError() - .body(Matchers.equalTo("params: p: pv, q: qv, h: 123, f: fv, m: mv, c: cv")); + .body(Matchers.equalTo("params: p: pv, q: qv, h: 123, f: fv, m: mv, c: cv, q2: empty, q3: 999")); RestAssured.get("/new-params/myklass/myregex/sse") .then() .log().ifError() diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index d7f9df67b824e..b8d1fda59443d 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -21,6 +21,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MATRIX_PARAM; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.NON_BLOCKING; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OPTIONAL; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH_PARAM; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH_SEGMENT; @@ -832,6 +833,13 @@ && isContextType(paramType.asClassType())) { if (convertible) { handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); } + } else if (pt.name().equals(OPTIONAL)) { + typeHandled = true; + elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index); + if (convertible) { + handleOptionalParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + } + builder.setOptional(true); } else if (convertible) { throw new RuntimeException("Invalid parameter type '" + pt + "' used on method " + errorLocation); } @@ -876,6 +884,10 @@ protected void handleSortedSetParam(Map existingConverters, Stri boolean hasRuntimeConverters, PARAM builder, String elementType) { } + protected void handleOptionalParam(Map existingConverters, String errorLocation, + boolean hasRuntimeConverters, PARAM builder, String elementType) { + } + protected void handleSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, PARAM builder, String elementType) { } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java index cfa62ad6deefe..18d98c36bb535 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java @@ -27,6 +27,7 @@ public class IndexedParameter> { protected ParameterType type; protected String elementType; protected boolean single; + protected boolean optional; public boolean isObtainedAsCollection() { return !single @@ -198,4 +199,12 @@ public T setSingle(boolean single) { return (T) this; } + public boolean isOptional() { + return optional; + } + + public T setOptional(boolean optional) { + this.optional = optional; + return (T) this; + } } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java index 5f191d6b3f9d7..ec6965199592e 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java @@ -14,6 +14,7 @@ public class MethodParameter { public boolean encoded; private boolean single; private String defaultValue; + private boolean optional; private boolean isObtainedAsCollection; public MethodParameter() { @@ -21,7 +22,7 @@ public MethodParameter() { public MethodParameter(String name, String type, String declaredType, String signature, ParameterType parameterType, boolean single, - String defaultValue, boolean isObtainedAsCollection, boolean encoded) { + String defaultValue, boolean isObtainedAsCollection, boolean optional, boolean encoded) { this.name = name; this.type = type; this.declaredType = declaredType; @@ -30,6 +31,7 @@ public MethodParameter(String name, String type, String declaredType, String sig this.single = single; this.defaultValue = defaultValue; this.isObtainedAsCollection = isObtainedAsCollection; + this.optional = optional; this.encoded = encoded; } @@ -88,6 +90,14 @@ public boolean isObtainedAsCollection() { return isObtainedAsCollection; } + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + public MethodParameter setObtainedAsCollection(boolean isObtainedAsCollection) { this.isObtainedAsCollection = isObtainedAsCollection; return this; diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java index 628f8bfbac458..89e04528a2397 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java @@ -38,6 +38,7 @@ import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; import org.jboss.resteasy.reactive.server.core.parameters.converters.ListConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.OptionalConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverterSupplier; import org.jboss.resteasy.reactive.server.core.parameters.converters.PathSegmentParamConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.SetConverter; @@ -230,7 +231,7 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas return new ServerMethodParameter(name, elementType, toClassName(paramType, currentClassInfo, actualEndpointInfo, index), type, single, signature, - converter, defaultValue, parameterResult.isObtainedAsCollection(), encoded, + converter, defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded, parameterResult.getCustomerParameterExtractor()); } @@ -247,6 +248,13 @@ protected void handleSortedSetParam(Map existingConverters, Stri builder.setConverter(new SortedSetConverter.SortedSetSupplier(converter)); } + protected void handleOptionalParam(Map existingConverters, String errorLocation, + boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { + ParameterConverterSupplier converter = extractConverter(elementType, index, + existingConverters, errorLocation, hasRuntimeConverters); + builder.setConverter(new OptionalConverter.OptionalSupplier(converter)); + } + protected void handleSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { ParameterConverterSupplier converter = extractConverter(elementType, index, diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java new file mode 100644 index 0000000000000..19698b2664c9e --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java @@ -0,0 +1,63 @@ +package org.jboss.resteasy.reactive.server.core.parameters.converters; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Optional; +import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; + +public class OptionalConverter implements ParameterConverter { + + private final ParameterConverter delegate; + + public OptionalConverter(ParameterConverter delegate) { + this.delegate = delegate; + } + + @Override + public Object convert(Object parameter) { + if (parameter == null) { + return Optional.empty(); + } else if (delegate != null) { + return Optional.ofNullable(delegate.convert(parameter)); + } else { + return Optional.of(parameter); + } + } + + @Override + public void init(ParamConverterProviders deployment, Class rawType, Type genericType, Annotation[] annotations) { + delegate.init(deployment, rawType, genericType, annotations); + } + + public static class OptionalSupplier implements DelegatingParameterConverterSupplier { + private ParameterConverterSupplier delegate; + + public OptionalSupplier() { + } + + public OptionalSupplier(ParameterConverterSupplier delegate) { + this.delegate = delegate; + } + + @Override + public ParameterConverter get() { + return delegate == null ? new OptionalConverter(null) : new OptionalConverter(delegate.get()); + } + + public ParameterConverterSupplier getDelegate() { + return delegate; + } + + public OptionalSupplier setDelegate(ParameterConverterSupplier delegate) { + this.delegate = delegate; + return this; + } + + @Override + public String getClassName() { + return OptionalConverter.class.getName(); + } + + } + +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index 945aec6d173f4..000497ab8c668 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -244,7 +244,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, handlers.add(new ParameterHandler(i, param.getDefaultValue(), extractor, converter, param.parameterType, - param.isObtainedAsCollection())); + param.isObtainedAsCollection(), param.isOptional())); } addHandlers(handlers, method, info, HandlerChainCustomizer.Phase.BEFORE_METHOD_INVOKE); handlers.add(new InvocationHandler(invoker)); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ParameterHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ParameterHandler.java index 5c267fdac76a6..4d5494b0bbf21 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ParameterHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ParameterHandler.java @@ -19,15 +19,17 @@ public class ParameterHandler implements ServerRestHandler { private final ParameterConverter converter; private final ParameterType parameterType; private final boolean isCollection; + private final boolean isOptional; public ParameterHandler(int index, String defaultValue, ParameterExtractor extractor, ParameterConverter converter, - ParameterType parameterType, boolean isCollection) { + ParameterType parameterType, boolean isCollection, boolean isOptional) { this.index = index; this.defaultValue = defaultValue; this.extractor = extractor; this.converter = converter; this.parameterType = parameterType; this.isCollection = isCollection; + this.isOptional = isOptional; } @Override @@ -65,7 +67,7 @@ private void handleResult(Object result, ResteasyReactiveRequestContext requestC result = defaultValue; } Throwable toThrow = null; - if (converter != null && result != null) { + if (converter != null && ((result != null) || isOptional)) { // spec says: /* * 3.2 Fields and Bean Properties diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java index 6f35a2a15bc61..85b99301a444a 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java @@ -15,9 +15,11 @@ public ServerMethodParameter() { public ServerMethodParameter(String name, String type, String declaredType, ParameterType parameterType, boolean single, String signature, - ParameterConverterSupplier converter, String defaultValue, boolean isObtainedAsCollection, boolean encoded, + ParameterConverterSupplier converter, String defaultValue, boolean isObtainedAsCollection, boolean isOptional, + boolean encoded, ParameterExtractor customerParameterExtractor) { - super(name, type, declaredType, signature, parameterType, single, defaultValue, isObtainedAsCollection, encoded); + super(name, type, declaredType, signature, parameterType, single, defaultValue, isObtainedAsCollection, isOptional, + encoded); this.converter = converter; this.customerParameterExtractor = customerParameterExtractor; }