From d6566f82da5a3810771b8fcb718d8bf0ddf2a8c9 Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Tue, 30 Jul 2024 18:37:26 -0400 Subject: [PATCH] Ignore JavaBean method prefixes when method name matches field name Signed-off-by: Michael Edgar --- .../scanner/dataobject/IgnoreResolver.java | 45 ++++++++++++------- .../scanner/dataobject/TypeResolver.java | 28 +++++++----- .../scanner/StandaloneSchemaScanTest.java | 31 +++++++++++++ ...ents.schemas.javabean-property-prefix.json | 18 ++++++++ 4 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.javabean-property-prefix.json diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java index 191a0eb7d..c38521e24 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -54,9 +55,10 @@ public enum Visibility { UNSET } - public Visibility isIgnore(AnnotationTarget annotationTarget, AnnotationTarget reference) { + public Visibility isIgnore(Map properties, + AnnotationTarget annotationTarget, AnnotationTarget reference) { for (IgnoreAnnotationHandler handler : ignoreHandlers) { - Visibility v = handler.shouldIgnore(annotationTarget, reference); + Visibility v = handler.shouldIgnore(properties, annotationTarget, reference); if (v != Visibility.UNSET) { return v; @@ -82,7 +84,8 @@ public Visibility getDescendantVisibility(String propertyName, List d */ private final class SchemaHiddenHandler implements IgnoreAnnotationHandler { @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { AnnotationInstance annotationInstance = context.annotations().getAnnotation(target, getNames()); if (annotationInstance != null) { Boolean hidden = context.annotations().value(annotationInstance, SchemaConstant.PROP_HIDDEN); @@ -105,7 +108,8 @@ public List getNames() { */ private final class JsonbTransientHandler implements IgnoreAnnotationHandler { @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { return context.annotations().hasAnnotation(target, getNames()) ? Visibility.IGNORED : Visibility.UNSET; } @@ -121,14 +125,15 @@ public List getNames() { private final class JsonIgnorePropertiesHandler implements IgnoreAnnotationHandler { @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { - Visibility visibility = declaringClassIgnore(target); + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { + Visibility visibility = declaringClassIgnore(properties, target); if (visibility != Visibility.UNSET) { return visibility; } - return nestingPropertyIgnore(reference, propertyName(target)); + return nestingPropertyIgnore(reference, propertyName(properties, target)); } /** @@ -146,10 +151,10 @@ public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget referen * @param target * @return */ - private Visibility declaringClassIgnore(AnnotationTarget target) { + private Visibility declaringClassIgnore(Map properties, AnnotationTarget target) { AnnotationInstance declaringClassJIP = context.annotations().getAnnotation(TypeUtil.getDeclaringClass(target), getNames()); - return shouldIgnoreTarget(declaringClassJIP, propertyName(target)); + return shouldIgnoreTarget(declaringClassJIP, propertyName(properties, target)); } /** @@ -181,12 +186,12 @@ private Visibility nestingPropertyIgnore(AnnotationTarget nesting, String proper return shouldIgnoreTarget(nestedTypeJIP, propertyName); } - private String propertyName(AnnotationTarget target) { + private String propertyName(Map properties, AnnotationTarget target) { if (target.kind() == Kind.FIELD) { return target.asField().name(); } // Assuming this is a getter or setter - return TypeResolver.propertyName(target.asMethod()); + return TypeResolver.propertyName(properties, target.asMethod()); } private Visibility shouldIgnoreTarget(AnnotationInstance jipAnnotation, String targetName) { @@ -227,7 +232,8 @@ public Visibility getDescendantVisibility(String propertyName, List d private final class JsonIgnoreHandler implements IgnoreAnnotationHandler { @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { AnnotationInstance annotationInstance = context.annotations().getAnnotation(target, getNames()); if (annotationInstance != null && valueAsBooleanOrTrue(annotationInstance)) { return Visibility.IGNORED; @@ -248,7 +254,8 @@ private final class JsonIgnoreTypeHandler implements IgnoreAnnotationHandler { private final Set ignoredTypes = new LinkedHashSet<>(); @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { Type classType; switch (target.kind()) { @@ -277,7 +284,7 @@ public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget referen // Primitive and non-indexed types will result in a null if (classType.kind() == Type.Kind.PRIMITIVE || classType.kind() == Type.Kind.VOID || - (classType.kind() == Type.Kind.ARRAY && classType.asArrayType().component().kind() == Type.Kind.PRIMITIVE) + (classType.kind() == Type.Kind.ARRAY && classType.asArrayType().constituent().kind() == Type.Kind.PRIMITIVE) || !index.containsClass(classType)) { return Visibility.UNSET; @@ -309,7 +316,8 @@ public List getNames() { private final class TransientIgnoreHandler implements IgnoreAnnotationHandler { @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { if (target.kind() == AnnotationTarget.Kind.FIELD) { FieldInfo field = target.asField(); // If field has transient modifier, e.g. `transient String foo;`, then hide it. @@ -336,7 +344,8 @@ public List getNames() { private final class JaxbAccessibilityHandler implements IgnoreAnnotationHandler { @Override - public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) { + public Visibility shouldIgnore(Map properties, AnnotationTarget target, + AnnotationTarget reference) { if (hasXmlTransient(target)) { return Visibility.IGNORED; } @@ -411,7 +420,9 @@ private boolean valueAsBooleanOrTrue(AnnotationInstance annotation) { } private interface IgnoreAnnotationHandler { - Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference); + Visibility shouldIgnore(Map properties, + AnnotationTarget target, + AnnotationTarget reference); List getNames(); diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java index e1808aa49..52b9106b8 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java @@ -559,7 +559,7 @@ private static List methods(AnnotationScannerContext context, ClassI } private static boolean acceptMethod(MethodInfo method) { - if (Modifier.isStatic(method.flags())) { + if (Modifier.isStatic(method.flags()) || method.isSynthetic()) { return false; } String name = method.name(); @@ -603,11 +603,11 @@ private static boolean isViewable(AnnotationScannerContext context, AnnotationTa * @param target the field or method to be checked for ignoring or exposure in the API * @param reference an annotated member (field or method) that referenced the type of target's declaring class * @param descendants list of classes that descend from the class containing target - * @param ignoreResolver resolver to determine if the field is ignored + * @param properties map of other known properties that are peers of the target */ private void processVisibility(AnnotationScannerContext context, AnnotationTarget target, AnnotationTarget reference, List descendants, - IgnoreResolver ignoreResolver) { + Map properties) { if (this.exposed || this.ignored) { // @Schema with hidden = false OR ignored somehow by a member lower in the class hierarchy return; @@ -619,7 +619,7 @@ private void processVisibility(AnnotationScannerContext context, AnnotationTarge return; } - switch (getVisibility(context, target, reference, descendants, ignoreResolver)) { + switch (getVisibility(context, target, reference, descendants, properties)) { case EXPOSED: this.exposed = true; break; @@ -674,22 +674,23 @@ private void processAccess(AnnotationTarget target) { * @param target the field or method to be checked for ignoring or exposure in the API * @param reference an annotated member (field or method) that referenced the type of target's declaring class * @param descendants list of classes that descend from the class containing target - * @param ignoreResolver resolver to determine if the field is ignored + * @param properties map of other known properties that are peers of the target */ private IgnoreResolver.Visibility getVisibility(AnnotationScannerContext context, AnnotationTarget target, AnnotationTarget reference, List descendants, - IgnoreResolver ignoreResolver) { + Map properties) { if (!isViewable(context, target)) { return IgnoreResolver.Visibility.IGNORED; } + IgnoreResolver ignoreResolver = context.getIgnoreResolver(); // First check if a descendant class has hidden/exposed the property IgnoreResolver.Visibility visibility = ignoreResolver.getDescendantVisibility(propertyName, descendants); if (visibility == IgnoreResolver.Visibility.UNSET) { - visibility = ignoreResolver.isIgnore(target, reference); + visibility = ignoreResolver.isIgnore(properties, target, reference); } return visibility; @@ -773,7 +774,7 @@ private static void scanField(AnnotationScannerContext context, Map properties, MethodInfo method) { final String methodName = method.name(); final ClassInfo declaringClass = method.declaringClass(); @@ -923,6 +924,11 @@ static String propertyName(MethodInfo method) { return methodName; } + if (Optional.ofNullable(properties.get(methodName)).map(p -> p.field).isPresent()) { + // The method name matches the field name. Do not modify further. + return methodName; + } + final int nameStart = methodNamePrefix(method).length(); final String propertyName; diff --git a/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java b/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java index 53009b282..5ba5d76d0 100644 --- a/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java +++ b/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java @@ -789,4 +789,35 @@ class Bean { assertJsonEquals("components.schemas.exceptional-examples.json", Bean.class); } + + @Test + void testPropertyWithJavaBeanPrefixes() throws IOException, JSONException { + @Schema(name = "Bean") + @SuppressWarnings("unused") + class Bean { + @Schema(name = "getProperty1") + public String getProperty1; + + @Schema(name = "propertyTwo") + public String getProperty2; + + public String getProperty1() { + return getProperty1; + } + + public void getProperty1(String getProperty1) { + this.getProperty1 = getProperty1; + } + + public String getProperty2() { + return getProperty2; + } + + public void getProperty2(String getProperty2) { + this.getProperty2 = getProperty2; + } + } + + assertJsonEquals("components.schemas.javabean-property-prefix.json", Bean.class); + } } diff --git a/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.javabean-property-prefix.json b/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.javabean-property-prefix.json new file mode 100644 index 000000000..58c822b7e --- /dev/null +++ b/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.javabean-property-prefix.json @@ -0,0 +1,18 @@ +{ + "openapi" : "3.0.3", + "components" : { + "schemas" : { + "Bean" : { + "type" : "object", + "properties" : { + "getProperty1" : { + "type" : "string" + }, + "propertyTwo" : { + "type" : "string" + } + } + } + } + } +}