Skip to content

Commit

Permalink
Remove need for Jakarta annotation for Spring param bean on GET methods
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Edgar <michael@xlate.io>
  • Loading branch information
MikeEdgar committed Nov 16, 2024
1 parent 75179df commit 72c1c1c
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ boolean nameAndStyleMatch(ParameterContext context, ParameterContextKey key) {
* @param target annotated item. Only method and method parameter targets.
* @return the MethodInfo associated with the target, or null if target is not a method or parameter.
*/
static MethodInfo targetMethod(AnnotationTarget target) {
protected static MethodInfo targetMethod(AnnotationTarget target) {
if (target.kind() == Kind.METHOD) {
return target.asMethod();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -228,40 +229,46 @@ private void processControllerMethods(final ClassInfo resourceClass,

for (MethodInfo methodInfo : getResourceMethods(context, resourceClass)) {
if (!methodInfo.annotations().isEmpty()) {
// Try @XXXMapping annotations
for (DotName validMethodAnnotations : SpringConstants.HTTP_METHODS) {
if (methodInfo.hasAnnotation(validMethodAnnotations)) {
String toHttpMethod = toHttpMethod(validMethodAnnotations);
PathItem.HttpMethod httpMethod = PathItem.HttpMethod.valueOf(toHttpMethod);
processControllerMethod(resourceClass, methodInfo, httpMethod, openApi, tagRefs,
locatorPathParameters);

}
for (PathItem.HttpMethod httpMethod : getHttpMethods(methodInfo)) {
processControllerMethod(resourceClass, methodInfo, httpMethod, openApi, tagRefs,
locatorPathParameters);
}
}
}
}

static Set<PathItem.HttpMethod> getHttpMethods(MethodInfo methodInfo) {
Set<PathItem.HttpMethod> methods = new LinkedHashSet<>();

// Try @RequestMapping
if (methodInfo.hasAnnotation(SpringConstants.REQUEST_MAPPING)) {
AnnotationInstance requestMappingAnnotation = methodInfo.annotation(SpringConstants.REQUEST_MAPPING);
AnnotationValue methodValue = requestMappingAnnotation.value("method");
if (methodValue != null) {
String[] enumArray = methodValue.asEnumArray();
for (String enumValue : enumArray) {
if (enumValue != null) {
PathItem.HttpMethod httpMethod = PathItem.HttpMethod.valueOf(enumValue.toUpperCase());
processControllerMethod(resourceClass, methodInfo, httpMethod, openApi, tagRefs,
locatorPathParameters);
}
}
} else {
// TODO: Default ?
// Try @XXXMapping annotations
for (DotName validMethodAnnotations : SpringConstants.HTTP_METHODS) {
if (methodInfo.hasAnnotation(validMethodAnnotations)) {
String toHttpMethod = toHttpMethod(validMethodAnnotations);
methods.add(PathItem.HttpMethod.valueOf(toHttpMethod));
}
}

// Try @RequestMapping
if (methodInfo.hasAnnotation(SpringConstants.REQUEST_MAPPING)) {
AnnotationInstance requestMappingAnnotation = methodInfo.annotation(SpringConstants.REQUEST_MAPPING);
AnnotationValue methodValue = requestMappingAnnotation.value("method");

if (methodValue != null) {
String[] enumArray = methodValue.asEnumArray();
for (String enumValue : enumArray) {
if (enumValue != null) {
methods.add(PathItem.HttpMethod.valueOf(enumValue.toUpperCase()));
}
}

} else {
// Default ?
}
}

return methods;
}

private String toHttpMethod(DotName dotname) {
private static String toHttpMethod(DotName dotname) {
String className = dotname.withoutPackagePrefix();
className = className.replace("Mapping", "");
return className.toUpperCase();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class SpringConstants {
static final DotName MUTIPART_FILE = DotName.createSimple("org.springframework.web.multipart.MultipartFile");

static final DotName QUERY_PARAM = DotName.createSimple("org.springframework.web.bind.annotation.RequestParam");
static final DotName JAKARTA_QUERY_PARAM = DotName.createSimple("jakarta.ws.rs.QueryParam");
static final DotName COOKIE_PARAM = DotName.createSimple("org.springframework.web.bind.annotation.CookieValue");
static final DotName PATH_PARAM = DotName.createSimple("org.springframework.web.bind.annotation.PathVariable");
static final DotName HEADER_PARAM = DotName.createSimple("org.springframework.web.bind.annotation.RequestHeader");
Expand All @@ -46,7 +45,7 @@ public class SpringConstants {
.collect(Collectors.toSet());

static final DotName PARAMETER_OBJECT = DotName.createSimple("org.springdoc.api.annotations.ParameterObject");

public static final Set<DotName> MULTIPART_OUTPUTS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(MUTIPART_FILE)));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public enum SpringParameter {
QUERY_PARAM(SpringConstants.QUERY_PARAM, Parameter.In.QUERY, null, Parameter.Style.FORM),
HEADER_PARAM(SpringConstants.HEADER_PARAM, Parameter.In.HEADER, null, Parameter.Style.SIMPLE),
COOKIE_PARAM(SpringConstants.COOKIE_PARAM, Parameter.In.COOKIE, null, Parameter.Style.FORM),
PARAMETER_OBJECT(SpringConstants.PARAMETER_OBJECT, null, null, null),
JAKARTA_QUERY_PARAM(SpringConstants.JAKARTA_QUERY_PARAM, Parameter.In.QUERY, null, Parameter.Style.FORM);
// SpringDoc annotation to indicate a bean with parameters (like Jakarta @BeanParam)
PARAMETER_OBJECT(SpringConstants.PARAMETER_OBJECT, null, null, null);

//BEAN_PARAM(SpringConstants.BEAN_PARAM, null, null, null),
//FORM_PARAM(SpringConstants.FORM_PARAM, null, Parameter.Style.FORM, Parameter.Style.FORM),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.function.Function;
import java.util.regex.Pattern;

import org.eclipse.microprofile.openapi.models.PathItem;
import org.eclipse.microprofile.openapi.models.parameters.Parameter;
import org.eclipse.microprofile.openapi.models.parameters.Parameter.Style;
import org.jboss.jandex.AnnotationInstance;
Expand All @@ -20,6 +21,7 @@
import io.smallrye.openapi.runtime.io.Names;
import io.smallrye.openapi.runtime.scanner.AnnotationScannerExtension;
import io.smallrye.openapi.runtime.scanner.ResourceParameters;
import io.smallrye.openapi.runtime.scanner.dataobject.TypeResolver;
import io.smallrye.openapi.runtime.scanner.spi.AbstractParameterProcessor;
import io.smallrye.openapi.runtime.scanner.spi.AnnotationScannerContext;
import io.smallrye.openapi.runtime.scanner.spi.FrameworkParameter;
Expand Down Expand Up @@ -122,20 +124,39 @@ protected void readAnnotatedType(AnnotationInstance annotation, AnnotationInstan
// }
} else if (frameworkParam.location != null) {
readFrameworkParameter(annotation, frameworkParam, overriddenParametersOnly);
} else if (target != null) {
// This is a @BeanParam or a RESTEasy @MultipartForm
} else if (target != null && annotatesHttpGET(target)) {
// This is a SpringDoc @ParameterObject
setMediaType(frameworkParam);
targetType = TypeUtil.unwrapType(targetType);

if (targetType != null) {
ClassInfo beanParam = index.getClassByName(targetType.name());
readParameters(beanParam, annotation, overriddenParametersOnly);

/*
* Since the properties of the bean are probably not annotated (supported in Spring),
* here we process them with a generated Spring @RequestParam annotation attached.
*/
for (var entry : TypeResolver.getAllFields(scannerContext, targetType, beanParam, null).entrySet()) {
var syntheticQuery = AnnotationInstance.builder(SpringConstants.QUERY_PARAM)
.buildWithTarget(entry.getValue().getAnnotationTarget());
readAnnotatedType(syntheticQuery, beanParamAnnotation, overriddenParametersOnly);
}
}
}
}
}
}

static boolean annotatesHttpGET(AnnotationTarget target) {
MethodInfo resourceMethod = targetMethod(target);

if (resourceMethod != null) {
return SpringAnnotationScanner.getHttpMethods(resourceMethod).contains(PathItem.HttpMethod.GET);
}

return false;
}

@Override
protected Set<DotName> getDefaultAnnotationNames() {
return Collections.singleton(SpringConstants.QUERY_PARAM);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package test.io.smallrye.openapi.runtime.scanner.entities;

import jakarta.ws.rs.QueryParam;

public class GreetingParam {
@QueryParam("nameQuery") // "real" spring does not require this, but quarkus-spring-web does
private final String name;

public GreetingParam(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Greeting helloParameterObject(@ParameterObject() GreetingParam params) {
// 5) ResponseEntity without a type specified
@GetMapping("/helloPathVariableWithResponse/{name}")
@APIResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(ref = "#/components/schemas/Greeting")))
public ResponseEntity helloPathVariableWithResponse(@PathVariable(name = "name") String name) {
public ResponseEntity<Greeting> helloPathVariableWithResponse(@PathVariable(name = "name") String name) {
return ResponseEntity.ok(new Greeting("Hello " + name));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
"/greeting/helloParameterObject": {
"get": {
"parameters" : [ {
"name" : "nameQuery",
"name" : "name",
"in" : "query",
"schema" : {
"type" : "string"
Expand Down

0 comments on commit 72c1c1c

Please sign in to comment.