[GERONIMO-6786] @BeanParam basic support
diff --git a/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessor.java b/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessor.java
index a4b9315..ecb7d13 100644
--- a/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessor.java
+++ b/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessor.java
@@ -50,6 +50,7 @@
import javax.json.JsonReaderFactory;
import javax.json.JsonValue;
import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.BeanParam;
import javax.ws.rs.CookieParam;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
@@ -96,6 +97,8 @@
import org.apache.geronimo.microprofile.openapi.impl.model.ServerVariableImpl;
import org.apache.geronimo.microprofile.openapi.impl.model.ServerVariablesImpl;
import org.apache.geronimo.microprofile.openapi.impl.model.TagImpl;
+import org.apache.geronimo.microprofile.openapi.impl.processor.reflect.ClassElement;
+import org.apache.geronimo.microprofile.openapi.impl.processor.reflect.FieldElement;
import org.apache.geronimo.microprofile.openapi.impl.processor.spi.NamingStrategy;
import org.eclipse.microprofile.openapi.OASConfig;
import org.eclipse.microprofile.openapi.annotations.Components;
@@ -385,9 +388,9 @@
});
operation.parameters(Stream.of(m.getParameters())
.filter(it -> it.isAnnotationPresent(Parameter.class) || hasJaxRsParams(it))
- .map(it -> buildParameter(it, api)
- .orElseGet(() -> new ParameterImpl().schema(schemaProcessor.mapSchemaFromClass(
- () -> getOrCreateComponents(api), it.getType()))))
+ .flatMap(it -> buildParameter(it, api)
+ .orElseGet(() -> Stream.of(new ParameterImpl().schema(schemaProcessor.mapSchemaFromClass(
+ () -> getOrCreateComponents(api), it.getType())))))
.filter(Objects::nonNull).collect(toList()));
Stream.of(m.getParameters())
.filter(it -> it.isAnnotationPresent(Parameters.class))
@@ -507,42 +510,73 @@
private boolean hasJaxRsParams(final AnnotatedElement it) {
return it.isAnnotationPresent(HeaderParam.class) || it.isAnnotationPresent(CookieParam.class) ||
- it.isAnnotationPresent(PathParam.class) || it.isAnnotationPresent(QueryParam.class);
+ it.isAnnotationPresent(PathParam.class) || it.isAnnotationPresent(QueryParam.class) ||
+ it.isAnnotationPresent(BeanParam.class);
}
- private Optional<org.eclipse.microprofile.openapi.models.parameters.Parameter> buildParameter(
+ private Optional<Stream<org.eclipse.microprofile.openapi.models.parameters.Parameter>> buildParameter(
final AnnotatedTypeElement annotatedElement, final OpenAPI openAPI) {
return ofNullable(ofNullable(annotatedElement.getAnnotation(Parameter.class))
.map(it -> mapParameter(annotatedElement, () -> getOrCreateComponents(openAPI), it))
+ .map(Stream::of)
.orElseGet(() -> {
if (hasJaxRsParams(annotatedElement)) {
- final ParameterImpl parameter = new ParameterImpl();
- if (annotatedElement.isAnnotationPresent(HeaderParam.class)) {
- parameter.in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.HEADER)
- .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.SIMPLE)
- .name(annotatedElement.getAnnotation(HeaderParam.class).value());
- } else if (annotatedElement.isAnnotationPresent(CookieParam.class)) {
- parameter.in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.COOKIE)
- .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.FORM)
- .name(annotatedElement.getAnnotation(CookieParam.class).value());
- } else if (annotatedElement.isAnnotationPresent(PathParam.class)) {
- parameter.required(true)
- .in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.PATH)
- .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.SIMPLE)
- .name(annotatedElement.getAnnotation(PathParam.class).value());
- } else if (annotatedElement.isAnnotationPresent(QueryParam.class)) {
- parameter.in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.QUERY)
- .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.FORM)
- .name(annotatedElement.getAnnotation(QueryParam.class).value());
+ if (annotatedElement.isAnnotationPresent(BeanParam.class)) {
+ return fromBeanParam(annotatedElement, openAPI);
}
- parameter.schema(schemaProcessor.mapSchemaFromClass(
- () -> getOrCreateComponents(openAPI), annotatedElement.getType()));
- return parameter;
+ return Stream.of(bindParam(annotatedElement, openAPI));
}
- return null;
+ return Stream.empty();
}));
}
+ private Stream<org.eclipse.microprofile.openapi.models.parameters.Parameter> fromBeanParam(
+ final AnnotatedTypeElement elt, final OpenAPI openAPI) {
+ final Type type = elt.getType();
+ if (type != null && type != Object.class) {
+ final Class<?> clazz = Class.class.cast(type);
+ return Stream.concat(fromBeanParamForType(clazz, openAPI), fromBeanParamForType(clazz.getSuperclass(), openAPI));
+ }
+ return null;
+ }
+
+ private Stream<org.eclipse.microprofile.openapi.models.parameters.Parameter> fromBeanParamForType(
+ final Class<?> type, final OpenAPI openAPI) {
+ if (type == null || type == Object.class) {
+ return Stream.empty();
+ }
+ return Stream.of(type.getDeclaredFields())
+ .filter(this::hasJaxRsParams)
+ .map(FieldElement::new)
+ .map(it -> bindParam(it, openAPI));
+ }
+
+ private ParameterImpl bindParam(final AnnotatedTypeElement annotatedElement,
+ final OpenAPI openAPI) {
+ final ParameterImpl parameter = new ParameterImpl();
+ if (annotatedElement.isAnnotationPresent(HeaderParam.class)) {
+ parameter.in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.HEADER)
+ .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.SIMPLE)
+ .name(annotatedElement.getAnnotation(HeaderParam.class).value());
+ } else if (annotatedElement.isAnnotationPresent(CookieParam.class)) {
+ parameter.in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.COOKIE)
+ .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.FORM)
+ .name(annotatedElement.getAnnotation(CookieParam.class).value());
+ } else if (annotatedElement.isAnnotationPresent(PathParam.class)) {
+ parameter.required(true)
+ .in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.PATH)
+ .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.SIMPLE)
+ .name(annotatedElement.getAnnotation(PathParam.class).value());
+ } else if (annotatedElement.isAnnotationPresent(QueryParam.class)) {
+ parameter.in(org.eclipse.microprofile.openapi.models.parameters.Parameter.In.QUERY)
+ .style(org.eclipse.microprofile.openapi.models.parameters.Parameter.Style.FORM)
+ .name(annotatedElement.getAnnotation(QueryParam.class).value());
+ }
+ parameter.schema(schemaProcessor.mapSchemaFromClass(
+ () -> getOrCreateComponents(openAPI), annotatedElement.getType()));
+ return parameter;
+ }
+
private PathItem getPathItem(final OpenAPI api, final String path) {
return api.getPaths().computeIfAbsent(path, p -> {
final PathItemImpl item = new PathItemImpl();
diff --git a/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/reflect/FieldElement.java b/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/reflect/FieldElement.java
new file mode 100644
index 0000000..39f41b2
--- /dev/null
+++ b/geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/reflect/FieldElement.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.geronimo.microprofile.openapi.impl.processor.reflect;
+
+import org.apache.geronimo.microprofile.openapi.impl.processor.AnnotatedTypeElement;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+
+public class FieldElement implements AnnotatedTypeElement {
+ private final Field delegate;
+ private Annotation[] annotations;
+
+ public FieldElement(final Field delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> annotationClass) {
+ return delegate.getAnnotation(annotationClass);
+ }
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return delegate.getAnnotations();
+ }
+
+ @Override
+ public Annotation[] getDeclaredAnnotations() {
+ return delegate.getDeclaredAnnotations();
+ }
+
+ @Override
+ public Type getType() {
+ return delegate.getGenericType();
+ }
+}
diff --git a/geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessorTest.java b/geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessorTest.java
index 378859d..8d2b733 100644
--- a/geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessorTest.java
+++ b/geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessorTest.java
@@ -23,14 +23,18 @@
import org.apache.geronimo.microprofile.openapi.impl.processor.spi.NamingStrategy;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.models.OpenAPI;
+import org.eclipse.microprofile.openapi.models.Operation;
import org.eclipse.microprofile.openapi.models.PathItem;
import org.eclipse.microprofile.openapi.models.parameters.Parameter;
import org.eclipse.microprofile.openapi.models.responses.APIResponses;
import org.testng.annotations.Test;
import javax.json.JsonPatch;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
import javax.ws.rs.PATCH;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -38,12 +42,12 @@
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
+import static java.util.stream.Collectors.joining;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -62,7 +66,7 @@
assertEquals("a", parameters.get(0).getName());
// TODO add more assertions
}
-
+
@Test
public void ensureParameterAnnotationsAreMerged() {
AnnotationProcessor annotationProcessor = new AnnotationProcessor(GeronimoOpenAPIConfig.create(), new NamingStrategy.Default(), null);
@@ -75,7 +79,7 @@
assertEquals(Parameter.In.QUERY, parameters.get(0).getIn());
assertEquals("b", parameters.get(0).getName());
}
-
+
@Test
public void ensureResponsesMediaTypeIsSetForDefaultResponses() {
AnnotationProcessor annotationProcessor = new AnnotationProcessor(GeronimoOpenAPIConfig.create(), new NamingStrategy.Default(), null);
@@ -91,7 +95,7 @@
assertNotNull(responses.get("204"));
assertNotNull(responses.get("204").getContent().get("text/plain"));
}
-
+
@Test
public void ensureResponsesMediaTypeIsSetForAllResponses() {
AnnotationProcessor annotationProcessor = new AnnotationProcessor(GeronimoOpenAPIConfig.create(), new NamingStrategy.Default(), null);
@@ -107,7 +111,7 @@
assertNotNull(responses.get("204"));
assertNotNull(responses.get("204").getContent().get("application/json"));
}
-
+
@Test
public void ensureResponsesDefaultMediaTypeIsSet() {
AnnotationProcessor annotationProcessor = new AnnotationProcessor(GeronimoOpenAPIConfig.create(), new NamingStrategy.Default(), null);
@@ -159,6 +163,20 @@
assertNotNull(openAPI.getPaths().get("/{a}").getPATCH().getOperationId()); // we didn't get an index exception
}
+ @Test
+ public void beanParam() {
+ final AnnotationProcessor annotationProcessor = new AnnotationProcessor((value, def) -> null, new NamingStrategy.Default(), null);
+ final OpenAPI openAPI = new OpenAPIImpl();
+ annotationProcessor.processClass("", openAPI, new ClassElement(Patched.class),
+ Stream.of(TestResource.class.getMethods()).map(MethodElement::new));
+ final Operation get = openAPI.getPaths().getPathItem("/beanparam").getGET();
+ assertNotNull(get);
+ assertEquals(2, get.getParameters().size());
+ assertEquals("header<=first(string),cookie<=second(string)", get.getParameters().stream()
+ .map(it -> it.getIn() + "<=" + it.getName() + "(" + it.getSchema().getType() + ")")
+ .collect(joining(",")));
+ }
+
@Path("/")
public class Patched {
@@ -190,19 +208,19 @@
public String hello(@PathParam("a") String a) {
return "hello";
}
-
+
@GET
@Path("/bye")
@Produces(MediaType.TEXT_PLAIN)
public void bye(@org.eclipse.microprofile.openapi.annotations.parameters.Parameter(required = true) @QueryParam("b") String b) {
}
-
+
@DELETE
@Path("/bye")
@APIResponse(responseCode = "204")
public void bye() {
}
-
+
@PATCH
@Path("/bye")
@Produces(MediaType.APPLICATION_JSON)
@@ -211,5 +229,19 @@
public Response bye(JsonPatch patch) {
return Response.ok().build();
}
+
+ @GET
+ @Path("/beanparam")
+ public Response beanParam(@BeanParam final Bound param) {
+ return Response.ok().build();
+ }
+ }
+
+ public static class Bound {
+ @HeaderParam("first")
+ private String premiere;
+
+ @CookieParam("second")
+ private String two;
}
}