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; }