From baa86271cb3034a243d99bfe92851f5c9e7c6b0c Mon Sep 17 00:00:00 2001 From: sam0r040 <93372330+sam0r040@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:00:25 +0200 Subject: [PATCH] Feat/handle no payload (#687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(core): handle no payload methods Co-authored-by: David Müller fix(core): extract AsyncMessage#description Co-authored-by: David Müller feat(core): use fqn in for schema name Co-authored-by: David Müller refactor(core): split test for PayloadClassExtractor and TypeToClassConverter Co-authored-by: Timon Back test(kafka): remove unused use-fqn setting Co-authored-by: David Müller test(kafka): update asyncapi.json Co-authored-by: David Müller refactor(core): extract TypeToClassConverter Co-authored-by: David Müller refactor(core): replace pair of resolved schema name and schema object with a specific record Co-authored-by: Timon Back feat(core): add NoPayloadUsedConsumer Co-authored-by: David Müller refactor(core): update AsyncHeadersBuilder (wip) Co-authored-by: David Müller feat(core): Use new PayloadService (wip) Co-authored-by: David Müller refactor(core): Check description in Schema annotation in DefaultComponentsService test(core): Add PayloadServiceTest refactor(core): Give PayloadService a name feat(core): Add PayloadNotUsed Schema Co-authored-by: Timon Back refactor(core): PayloadClassExtractor returns optional instead of throwing an exception Co-authored-by: Timon Back diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java index 9ccf6e1c..e6302005 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java @@ -7,10 +7,12 @@ import io.github.springwolf.asyncapi.v3.model.schema.MultiFormatSchema; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @JsonSerialize(using = MessagePayloadSerializer.class) @EqualsAndHashCode +@ToString public class MessagePayload { private MultiFormatSchema multiFormatSchema; private SchemaObject schema; diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java index b938b4d4..bc6b16b9 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java @@ -9,6 +9,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; @EqualsAndHashCode +@ToString @NoArgsConstructor @AllArgsConstructor public class MessageReference implements Message, Reference { diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java index 710a3ed0..497fb78e 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java @@ -10,6 +10,7 @@ import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import java.math.BigDecimal; import java.util.List; @@ -31,6 +32,7 @@ import java.util.Map; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = true) +@ToString public class SchemaObject extends ExtendableObject implements Schema { /** * Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java index 2384a153..848c27fb 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java @@ -9,6 +9,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; @EqualsAndHashCode +@ToString @NoArgsConstructor @AllArgsConstructor public class SchemaReference implements Schema, Reference { diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java index 9c982b3d..f5132257 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java @@ -12,6 +12,8 @@ public interface ComponentsService { Map getSchemas(); + SchemaObject resolveSchema(String schemaName); + String registerSchema(SchemaObject headers); String registerSchema(Class type); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java index 59caf4e5..f2e3104c 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java @@ -14,6 +14,7 @@ import io.swagger.v3.core.jackson.TypeNameResolver; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; +import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -60,6 +61,15 @@ public class DefaultComponentsService implements ComponentsService { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + @Override + @Nullable + public SchemaObject resolveSchema(String schemaName) { + if (schemas.containsKey(schemaName)) { + return swaggerSchemaUtil.mapSchema(schemas.get(schemaName)); + } + return null; + } + @Override public String registerSchema(SchemaObject headers) { log.debug("Registering schema for {}", headers.getTitle()); @@ -81,6 +91,7 @@ public class DefaultComponentsService implements ComponentsService { @Override public String registerSchema(Class type) { + // FIXME: Move this to the new HeadersService return this.registerSchema(type, properties.getDocket().getDefaultContentType()); } @@ -90,13 +101,13 @@ public class DefaultComponentsService implements ComponentsService { String actualContentType = StringUtils.isBlank(contentType) ? properties.getDocket().getDefaultContentType() : contentType; - Map schemas = new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type))); + Map newSchemas = new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type))); - String schemaName = getSchemaName(type, schemas); + String schemaName = getSchemaName(type, newSchemas); - preProcessSchemas(schemas, schemaName, type); - schemas.forEach(this.schemas::putIfAbsent); - schemas.values().forEach(schema -> postProcessSchema(schema, actualContentType)); + preProcessSchemas(newSchemas, schemaName, type); + newSchemas.forEach(this.schemas::putIfAbsent); + newSchemas.values().forEach(schema -> postProcessSchema(schema, actualContentType)); return schemaName; } @@ -130,11 +141,22 @@ public class DefaultComponentsService implements ComponentsService { return new ArrayList<>(resolvedPayloadModelName).get(0); } - return type.getSimpleName(); + return getNameFromClass(type); } private void preProcessSchemas(Map schemas, String schemaName, Class type) { processAsyncApiPayloadAnnotation(schemas, schemaName, type); + processSchemaAnnotation(schemas, schemaName, type); + } + + private void processSchemaAnnotation(Map schemas, String schemaName, Class type) { + Schema schemaForType = schemas.get(schemaName); + if (schemaForType != null) { + var schemaAnnotation = type.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); + if (schemaAnnotation != null) { + schemaForType.setDescription(schemaAnnotation.description()); + } + } } private void processAsyncApiPayloadAnnotation(Map schemas, String schemaName, Class type) { @@ -161,9 +183,9 @@ public class DefaultComponentsService implements ComponentsService { } private String registerString() { - String schemaName = "String"; + String schemaName = getNameFromClass(String.class); StringSchema schema = new StringSchema(); - schema.setName(String.class.getName()); + schema.setName(schemaName); this.schemas.put(schemaName, schema); postProcessSchema(schema, DEFAULT_CONTENT_TYPE); @@ -181,6 +203,13 @@ public class DefaultComponentsService implements ComponentsService { return result; } + private String getNameFromClass(Class type) { + if (properties.isUseFqn()) { + return type.getName(); + } + return type.getSimpleName(); + } + private void postProcessSchema(Schema schema, String contentType) { for (SchemasPostProcessor processor : schemaPostProcessors) { processor.process(schema, schemas, contentType); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java index 195fcb13..0496125f 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java @@ -2,7 +2,8 @@ package io.github.springwolf.core.asyncapi.components.headers; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; public interface AsyncHeadersBuilder { - SchemaObject buildHeaders(Class payloadType); + SchemaObject buildHeaders(NamedSchemaObject payloadSchema); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java index b1548c4e..77b177bf 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java @@ -2,6 +2,7 @@ package io.github.springwolf.core.asyncapi.components.headers; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import java.util.List; import java.util.Map; @@ -22,7 +23,7 @@ public class AsyncHeadersNotDocumented implements AsyncHeadersBuilder { } @Override - public SchemaObject buildHeaders(Class payloadType) { + public SchemaObject buildHeaders(NamedSchemaObject payloadSchema) { return NOT_DOCUMENTED; } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java index 19ffed26..8b026f07 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java @@ -2,6 +2,7 @@ package io.github.springwolf.core.asyncapi.components.headers; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import java.util.List; import java.util.Map; @@ -21,7 +22,7 @@ public class AsyncHeadersNotUsed implements AsyncHeadersBuilder { } @Override - public SchemaObject buildHeaders(Class payloadType) { + public SchemaObject buildHeaders(NamedSchemaObject payloadSchema) { return NOT_USED; } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java index 253e1c7c..7c28ddab 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java @@ -15,6 +15,7 @@ import io.github.springwolf.core.asyncapi.scanners.bindings.operations.Operation import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AsyncAnnotationUtil; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import lombok.extern.slf4j.Slf4j; @@ -36,11 +37,13 @@ public class AsyncAnnotationChannelsScanner extends AsyncA ComponentsService componentsService, AsyncApiDocketService asyncApiDocketService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { super( asyncAnnotationProvider, payloadClassExtractor, + payloadService, componentsService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java index d86fff29..22d88226 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java @@ -8,7 +8,7 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.ClassLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -30,14 +30,14 @@ public class SpringAnnotationClassLevelChannelsScanner< Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super( classAnnotationClass, methodAnnotationClass, bindingFactory, asyncHeadersBuilder, - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java index c2942147..2321fa21 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java @@ -9,7 +9,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.MethodLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -25,17 +26,17 @@ public class SpringAnnotationMethodLevelChannelsScanner implements SpringAnnotationChannelsScannerDelegator { private final Class methodAnnotationClass; - private final PayloadClassExtractor payloadClassExtractor; + private final PayloadService payloadService; public SpringAnnotationMethodLevelChannelsScanner( Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super(bindingFactory, asyncHeadersBuilder, componentsService); this.methodAnnotationClass = methodAnnotationClass; - this.payloadClassExtractor = payloadClassExtractor; + this.payloadService = payloadService; } @Override @@ -56,16 +57,15 @@ public class SpringAnnotationMethodLevelChannelsScanner payload = payloadClassExtractor.extractFrom(method); - - ChannelObject channelItem = buildChannelItem(annotation, payload); - return Map.entry(channelName, channelItem); } - private ChannelObject buildChannelItem(MethodAnnotation annotation, Class payloadType) { - MessageObject message = buildMessage(annotation, payloadType); + private ChannelObject buildChannelItem(MethodAnnotation annotation, NamedSchemaObject payloadSchema) { + MessageObject message = buildMessage(annotation, payloadSchema); return buildChannelItem(annotation, message); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java index 0dbba50d..48884d26 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java @@ -17,7 +17,9 @@ import io.github.springwolf.core.asyncapi.annotations.AsyncOperation; import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.bindings.messages.MessageBindingProcessor; import io.github.springwolf.core.asyncapi.scanners.bindings.operations.OperationBindingProcessor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.common.utils.AsyncAnnotationUtil; import io.github.springwolf.core.asyncapi.scanners.common.utils.TextUtils; @@ -42,6 +44,7 @@ public abstract class AsyncAnnotationScanner implements Em protected final AsyncAnnotationProvider asyncAnnotationProvider; protected final PayloadClassExtractor payloadClassExtractor; + protected final PayloadService payloadService; protected final ComponentsService componentsService; protected final List operationBindingProcessors; protected final List messageBindingProcessors; @@ -89,30 +92,33 @@ public abstract class AsyncAnnotationScanner implements Em } protected MessageObject buildMessage(AsyncOperation operationData, Method method) { - Class payloadType = operationData.payloadType() != Object.class - ? operationData.payloadType() - : payloadClassExtractor.extractFrom(method); + NamedSchemaObject payloadSchema = payloadService.extractSchema(operationData, method); - String modelName = this.componentsService.registerSchema( - payloadType, operationData.message().contentType()); - SchemaObject asyncHeaders = AsyncAnnotationUtil.getAsyncHeaders(operationData, resolver); - String headerModelName = this.componentsService.registerSchema(asyncHeaders); - var headers = MessageHeaders.of(MessageReference.toSchema(headerModelName)); - - var schema = payloadType.getAnnotation(Schema.class); - String description = schema != null ? schema.description() : null; + // TODO: move block to own HeaderService + SchemaObject headerSchema = AsyncAnnotationUtil.getAsyncHeaders(operationData, resolver); + String headerSchemaName = this.componentsService.registerSchema(headerSchema); + var headers = MessageHeaders.of(MessageReference.toSchema(headerSchemaName)); Map messageBinding = AsyncAnnotationUtil.processMessageBindingFromAnnotation(method, messageBindingProcessors); var messagePayload = MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(modelName)) + .schema(SchemaReference.fromSchema(payloadSchema.name())) .build()); + String description = operationData.message().description(); + if (!StringUtils.hasText(description)) { + description = payloadSchema.schema().getDescription(); + } + if (StringUtils.hasText(description)) { + description = this.resolver.resolveStringValue(description); + description = TextUtils.trimIndent(description); + } + var builder = MessageObject.builder() - .messageId(payloadType.getName()) - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) + .messageId(payloadSchema.name()) + .name(payloadSchema.name()) + .title(payloadSchema.schema().getTitle()) .description(description) .payload(messagePayload) .headers(headers) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java index f814e939..8a7f6507 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java @@ -11,7 +11,8 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference; import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner; import lombok.RequiredArgsConstructor; @@ -37,7 +38,7 @@ public abstract class ClassLevelAnnotationScanner< protected final Class methodAnnotationClass; protected final BindingFactory bindingFactory; protected final AsyncHeadersBuilder asyncHeadersBuilder; - protected final PayloadClassExtractor payloadClassExtractor; + protected final PayloadService payloadService; protected final ComponentsService componentsService; protected enum MessageType { @@ -67,8 +68,8 @@ public abstract class ClassLevelAnnotationScanner< SpringAnnotationClassLevelOperationsScanner.MessageType messageType) { Set messages = methods.stream() .map((Method method) -> { - Class payloadType = payloadClassExtractor.extractFrom(method); - return buildMessage(classAnnotation, payloadType); + NamedSchemaObject payloadSchema = payloadService.extractSchema(method); + return buildMessage(classAnnotation, payloadSchema); }) .collect(toSet()); @@ -79,18 +80,19 @@ public abstract class ClassLevelAnnotationScanner< return toMessagesMap(messages); } - protected MessageObject buildMessage(ClassAnnotation classAnnotation, Class payloadType) { + protected MessageObject buildMessage(ClassAnnotation classAnnotation, NamedSchemaObject payloadSchema) { Map messageBinding = bindingFactory.buildMessageBinding(classAnnotation); - String modelName = componentsService.registerSchema(payloadType); - String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType)); + + String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadSchema)); + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(modelName)) + .schema(SchemaReference.fromSchema(payloadSchema.name())) .build()); MessageObject message = MessageObject.builder() - .messageId(payloadType.getName()) - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) + .messageId(payloadSchema.name()) + .name(payloadSchema.name()) + .title(payloadSchema.schema().getTitle()) .description(null) .payload(payload) .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java index 1d548122..72f5e823 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java @@ -11,6 +11,7 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference; import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,18 +26,20 @@ public abstract class MethodLevelAnnotationScanner payloadType) { + protected MessageObject buildMessage(MethodAnnotation annotation, NamedSchemaObject payloadSchema) { Map messageBinding = bindingFactory.buildMessageBinding(annotation); - String modelName = componentsService.registerSchema(payloadType); - String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType)); + + // TODO: move block to own HeaderService + String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadSchema)); + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(modelName)) + .schema(SchemaReference.fromSchema(payloadSchema.name())) .build()); MessageObject message = MessageObject.builder() - .messageId(payloadType.getName()) - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) + .messageId(payloadSchema.name()) + .name(payloadSchema.name()) + .title(payloadSchema.schema().getTitle()) .description(null) .payload(payload) .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java new file mode 100644 index 00000000..9509becf --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; + +/** + * Encapsulates the resolved name for the contained schema. + * @param name The fully qualified name or the simple name of the schema. + * @param schema The SchemaObject. + */ +public record NamedSchemaObject(String name, SchemaObject schema) {} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java index 0d19c5e6..9e80ef74 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java @@ -1,52 +1,34 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.core.asyncapi.scanners.common.payload; -import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.Payload; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.Arrays; -import java.util.Map; +import java.util.Optional; +@RequiredArgsConstructor @Slf4j public class PayloadClassExtractor { private final TypeToClassConverter typeToClassConverter; - public PayloadClassExtractor(SpringwolfConfigProperties properties) { - Map extractableClasses = Map.of(); - if (properties.getPayload() != null) { - extractableClasses = properties.getPayload().getExtractableClasses(); - } - typeToClassConverter = new TypeToClassConverter(extractableClasses); - } - - public Class extractFrom(Method method) { + public Optional> extractFrom(Method method) { String methodName = String.format("%s::%s", method.getDeclaringClass().getSimpleName(), method.getName()); log.debug("Finding payload type for {}", methodName); - int parameterPayloadIndex = - getPayloadParameterIndex(method.getParameterTypes(), method.getParameterAnnotations(), methodName); - - return typeToClassConverter.extractClass(method.getGenericParameterTypes()[parameterPayloadIndex]); + return getPayloadParameterIndex(method.getParameterTypes(), method.getParameterAnnotations(), methodName) + .map((parameterPayloadIndex) -> + typeToClassConverter.extractClass(method.getGenericParameterTypes()[parameterPayloadIndex])); } - public Class typeToClass(Type type) { - return typeToClassConverter.extractClass(type); - } - - private int getPayloadParameterIndex( + private Optional getPayloadParameterIndex( Class[] parameterClasses, Annotation[][] parameterAnnotations, String methodName) { - switch (parameterClasses.length) { - case 0 -> throw new IllegalArgumentException( - "Payload cannot be detected. Method must not have 0 parameters: " + methodName); - case 1 -> { - return 0; - } + return switch (parameterClasses.length) { + case 0 -> Optional.empty(); + case 1 -> Optional.of(0); default -> { int payloadAnnotatedParameterIndex = getPayloadAnnotatedParameterIndex(parameterAnnotations); if (payloadAnnotatedParameterIndex == -1) { @@ -57,9 +39,9 @@ public class PayloadClassExtractor { throw new IllegalArgumentException(msg); } - return payloadAnnotatedParameterIndex; + yield Optional.of(payloadAnnotatedParameterIndex); } - } + }; } private int getPayloadAnnotatedParameterIndex(Annotation[][] parameterAnnotations) { @@ -74,58 +56,4 @@ public class PayloadClassExtractor { return -1; } - - @RequiredArgsConstructor - private static class TypeToClassConverter { - - private final Map extractableClassToArgumentIndex; - - private Class extractClass(Type parameterType) { - try { - if (parameterType instanceof ParameterizedType) { - Type rawParameterType = ((ParameterizedType) parameterType).getRawType(); - String rawParameterTypeName = rawParameterType.getTypeName(); - - Class actualPayloadClass = - extractActualGenericClass((ParameterizedType) parameterType, rawParameterTypeName); - if (actualPayloadClass != Void.class) { - return actualPayloadClass; - } - - // nested generic class - fallback to most outer container - return Class.forName(rawParameterTypeName); - } - - // no generics used - just a normal type - return Class.forName(parameterType.getTypeName()); - } catch (Exception ex) { - log.info("Unable to extract generic data type of {}", parameterType, ex); - } - return Void.class; - } - - private Class extractActualGenericClass(ParameterizedType parameterType, String rawParameterTypeName) { - Type type = parameterType; - String typeName = rawParameterTypeName; - - while (type instanceof ParameterizedType && extractableClassToArgumentIndex.containsKey(typeName)) { - Integer index = extractableClassToArgumentIndex.get(rawParameterTypeName); - - type = ((ParameterizedType) type).getActualTypeArguments()[index]; - - typeName = type.getTypeName(); - if (type instanceof ParameterizedType) { - typeName = ((ParameterizedType) type).getRawType().getTypeName(); - } - } - - try { - return Class.forName(typeName); - } catch (ClassNotFoundException ex) { - log.debug("Unable to find class for type {}", typeName, ex); - } - - return Void.class; - } - } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java new file mode 100644 index 00000000..69342a90 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.annotations.AsyncOperation; +import io.github.springwolf.core.asyncapi.components.ComponentsService; +import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RequiredArgsConstructor +public class PayloadService { + private final PayloadClassExtractor payloadClassExtractor; + private final ComponentsService componentsService; + private final SpringwolfConfigProperties properties; + + private static final String PAYLOAD_NOT_USED_KEY = "PayloadNotUsed"; + static final NamedSchemaObject PAYLOAD_NOT_USED = new NamedSchemaObject( + PAYLOAD_NOT_USED_KEY, + SchemaObject.builder() + .title(PAYLOAD_NOT_USED_KEY) + .description("No payload specified") + .properties(Map.of()) + .build()); + + public NamedSchemaObject extractSchema(Method method) { + Optional> payloadType = payloadClassExtractor.extractFrom(method); + + String contentType = properties.getDocket().getDefaultContentType(); + return payloadType.map((type) -> buildSchema(contentType, type)).orElseGet(this::useUnusedPayload); + } + + public NamedSchemaObject extractSchema(AsyncOperation operationData, Method method) { + Optional> payloadType = operationData.payloadType() != Object.class + ? Optional.of(operationData.payloadType()) + : payloadClassExtractor.extractFrom(method); + + String contentType = operationData.message().contentType(); + return payloadType.map((type) -> buildSchema(contentType, type)).orElseGet(this::useUnusedPayload); + } + + private NamedSchemaObject buildSchema(String contentType, Class payloadType) { + String componentsSchemaName = this.componentsService.registerSchema(payloadType, contentType); + + SchemaObject schema = componentsService.resolveSchema(componentsSchemaName); + schema.setTitle(payloadType.getSimpleName()); + + return new NamedSchemaObject(componentsSchemaName, schema); + } + + private NamedSchemaObject useUnusedPayload() { + this.componentsService.registerSchema(PAYLOAD_NOT_USED.schema()); + return PAYLOAD_NOT_USED; + } +} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java new file mode 100644 index 00000000..c55f8254 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +@Slf4j +public class TypeToClassConverter { + + private final Map extractableClassToArgumentIndex; + + public TypeToClassConverter(SpringwolfConfigProperties properties) { + if (properties.getPayload() != null) { + extractableClassToArgumentIndex = properties.getPayload().getExtractableClasses(); + } else { + extractableClassToArgumentIndex = Map.of(); + } + } + + public Class extractClass(Type parameterType) { + try { + if (parameterType instanceof ParameterizedType) { + Type rawParameterType = ((ParameterizedType) parameterType).getRawType(); + String rawParameterTypeName = rawParameterType.getTypeName(); + + Class actualPayloadClass = + extractActualGenericClass((ParameterizedType) parameterType, rawParameterTypeName); + if (actualPayloadClass != Void.class) { + return actualPayloadClass; + } + + // nested generic class - fallback to most outer container + return Class.forName(rawParameterTypeName); + } + + // no generics used - just a normal type + return Class.forName(parameterType.getTypeName()); + } catch (Exception ex) { + log.info("Unable to extract generic data type of %s".formatted(parameterType), ex); + } + return Void.class; + } + + private Class extractActualGenericClass(ParameterizedType parameterType, String rawParameterTypeName) { + Type type = parameterType; + String typeName = rawParameterTypeName; + + while (type instanceof ParameterizedType && extractableClassToArgumentIndex.containsKey(typeName)) { + Integer index = extractableClassToArgumentIndex.get(rawParameterTypeName); + + type = ((ParameterizedType) type).getActualTypeArguments()[index]; + + typeName = type.getTypeName(); + if (type instanceof ParameterizedType) { + typeName = ((ParameterizedType) type).getRawType().getTypeName(); + } + } + + try { + return Class.forName(typeName); + } catch (ClassNotFoundException ex) { + log.debug("Unable to find class for type %s".formatted(typeName), ex); + } + + return Void.class; + } +} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java index 6cee589b..16ac6204 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java @@ -10,6 +10,7 @@ import io.github.springwolf.core.asyncapi.scanners.bindings.operations.Operation import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.OperationMerger; import lombok.extern.slf4j.Slf4j; @@ -28,11 +29,13 @@ public class AsyncAnnotationOperationsScanner extends Asyn ClassScanner classScanner, ComponentsService componentsService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { super( asyncAnnotationProvider, payloadClassExtractor, + payloadService, componentsService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java index 74b0db0c..81166950 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java @@ -10,7 +10,7 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.ClassLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -32,14 +32,14 @@ public class SpringAnnotationClassLevelOperationsScanner< Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super( classAnnotationClass, methodAnnotationClass, bindingFactory, asyncHeadersBuilder, - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java index e222893f..ac5448ac 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java @@ -11,7 +11,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.MethodLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -28,17 +29,17 @@ public class SpringAnnotationMethodLevelOperationsScanner implements SpringAnnotationOperationsScannerDelegator { private final Class methodAnnotationClass; - private final PayloadClassExtractor payloadClassExtractor; + private final PayloadService payloadService; public SpringAnnotationMethodLevelOperationsScanner( Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super(bindingFactory, asyncHeadersBuilder, componentsService); this.methodAnnotationClass = methodAnnotationClass; - this.payloadClassExtractor = payloadClassExtractor; + this.payloadService = payloadService; } @Override @@ -61,13 +62,13 @@ public class SpringAnnotationMethodLevelOperationsScanner payload = payloadClassExtractor.extractFrom(method); + NamedSchemaObject payloadSchema = payloadService.extractSchema(method); - Operation operation = buildOperation(annotation, payload); + Operation operation = buildOperation(annotation, payloadSchema); return Map.entry(operationId, operation); } - private Operation buildOperation(MethodAnnotation annotation, Class payloadType) { + private Operation buildOperation(MethodAnnotation annotation, NamedSchemaObject payloadType) { MessageObject message = buildMessage(annotation, payloadType); return buildOperation(annotation, message); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java index b8284b26..2851142c 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java @@ -28,6 +28,8 @@ import io.github.springwolf.core.asyncapi.operations.OperationsService; import io.github.springwolf.core.asyncapi.scanners.ChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.OperationsScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.configuration.docket.DefaultAsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants; @@ -161,7 +163,22 @@ public class SpringwolfAutoConfiguration { @Bean @ConditionalOnMissingBean - public PayloadClassExtractor payloadClassExtractor(SpringwolfConfigProperties springwolfConfigProperties) { - return new PayloadClassExtractor(springwolfConfigProperties); + public TypeToClassConverter typeToClassConverter(SpringwolfConfigProperties springwolfConfigProperties) { + return new TypeToClassConverter(springwolfConfigProperties); + } + + @Bean + @ConditionalOnMissingBean + public PayloadClassExtractor payloadClassExtractor(TypeToClassConverter typeToClassConverter) { + return new PayloadClassExtractor(typeToClassConverter); + } + + @Bean + @ConditionalOnMissingBean + public PayloadService payloadService( + PayloadClassExtractor payloadClassExtractor, + ComponentsService componentsService, + SpringwolfConfigProperties properties) { + return new PayloadService(payloadClassExtractor, componentsService, properties); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java index a0c9e7d8..ab416da7 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java @@ -17,6 +17,7 @@ import io.github.springwolf.core.asyncapi.scanners.classes.spring.ComponentClass import io.github.springwolf.core.asyncapi.scanners.classes.spring.ConfigurationClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.AsyncAnnotationOperationsScanner; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -74,6 +75,7 @@ public class SpringwolfScannerConfiguration { ComponentsService componentsService, AsyncApiDocketService asyncApiDocketService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationChannelsScanner<>( @@ -82,6 +84,7 @@ public class SpringwolfScannerConfiguration { componentsService, asyncApiDocketService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } @@ -96,6 +99,7 @@ public class SpringwolfScannerConfiguration { SpringwolfClassScanner springwolfClassScanner, ComponentsService componentsService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationOperationsScanner<>( @@ -103,6 +107,7 @@ public class SpringwolfScannerConfiguration { springwolfClassScanner, componentsService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } @@ -118,6 +123,7 @@ public class SpringwolfScannerConfiguration { ComponentsService componentsService, AsyncApiDocketService asyncApiDocketService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationChannelsScanner<>( @@ -126,6 +132,7 @@ public class SpringwolfScannerConfiguration { componentsService, asyncApiDocketService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } @@ -140,6 +147,7 @@ public class SpringwolfScannerConfiguration { SpringwolfClassScanner springwolfClassScanner, ComponentsService componentsService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationOperationsScanner<>( @@ -147,6 +155,7 @@ public class SpringwolfScannerConfiguration { springwolfClassScanner, componentsService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java index 39b2002d..7e4bdb5c 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java @@ -28,6 +28,8 @@ import io.github.springwolf.core.asyncapi.scanners.channels.AsyncAnnotationChann import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocket; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; @@ -85,7 +87,10 @@ class AsyncAnnotationChannelsScannerTest { new DefaultComponentsService(emptyList(), emptyList(), swaggerSchemaUtil, properties); private final AsyncApiDocketService asyncApiDocketService = mock(AsyncApiDocketService.class); - private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(properties); + private final TypeToClassConverter typeToClassConverter = new TypeToClassConverter(properties); + private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(typeToClassConverter); + private final PayloadService payloadService = + new PayloadService(payloadClassExtractor, componentsService, properties); private final List operationBindingProcessors = List.of(new TestOperationBindingProcessor()); @@ -99,6 +104,7 @@ class AsyncAnnotationChannelsScannerTest { componentsService, asyncApiDocketService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java index 11e2dec6..3671c992 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java @@ -20,6 +20,8 @@ import io.github.springwolf.core.asyncapi.components.examples.walkers.json.Examp import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import lombok.Data; import lombok.EqualsAndHashCode; @@ -48,7 +50,9 @@ import static org.assertj.core.api.Assertions.assertThat; SpringAnnotationClassLevelChannelsScannerIntegrationTest.TestBindingFactory.class, DefaultComponentsService.class, SwaggerSchemaUtil.class, + PayloadService.class, PayloadClassExtractor.class, + TypeToClassConverter.class, DefaultSchemaWalker.class, SchemaWalkerProvider.class, ExampleJsonValueGenerator.class, @@ -60,7 +64,7 @@ class SpringAnnotationClassLevelChannelsScannerIntegrationTest { BindingFactory bindingFactory; @Autowired - PayloadClassExtractor payloadClassExtractor; + PayloadService payloadService; @Autowired ComponentsService componentsService; @@ -74,7 +78,7 @@ class SpringAnnotationClassLevelChannelsScannerIntegrationTest { TestMethodListener.class, this.bindingFactory, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java index 9f0ba78b..601cf424 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java @@ -18,7 +18,8 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference; import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.jupiter.api.BeforeEach; @@ -38,7 +39,7 @@ import static org.mockito.Mockito.when; class SpringAnnotationClassLevelChannelsScannerTest { - private final PayloadClassExtractor payloadClassExtractor = mock(PayloadClassExtractor.class); + pri… * chore: spotless Co-authored-by: David Müller * chore: update gradle dependencies Co-authored-by: David Müller * fix(common-model-converters): restore original class name as schema name Co-authored-by: Timon Back * feat(core): resolve model converters ref correctly * refactor(common-model-converters): restructure MonetaryAmountConverter to make clearer what happens before and after proceeding the chain * chore(core): prevent adding null values to schema map in processCommonModelConverters --------- Co-authored-by: Timon Back --- .../MonetaryAmountConverter.java | 30 ++- .../MonetaryAmountConverterTest.java | 23 ++- .../model/channel/message/MessagePayload.java | 2 + .../channel/message/MessageReference.java | 1 + .../v3/model/schema/SchemaObject.java | 2 + .../v3/model/schema/SchemaReference.java | 1 + .../components/ComponentsService.java | 2 + .../components/DefaultComponentsService.java | 63 +++++- .../headers/AsyncHeadersBuilder.java | 3 +- .../headers/AsyncHeadersNotDocumented.java | 3 +- .../headers/AsyncHeadersNotUsed.java | 3 +- .../AsyncAnnotationChannelsScanner.java | 3 + ...ngAnnotationClassLevelChannelsScanner.java | 6 +- ...gAnnotationMethodLevelChannelsScanner.java | 20 +- .../common/AsyncAnnotationScanner.java | 37 ++-- .../common/ClassLevelAnnotationScanner.java | 24 ++- .../common/MethodLevelAnnotationScanner.java | 17 +- .../common/payload/NamedSchemaObject.java | 11 + .../common/payload/PayloadClassExtractor.java | 96 ++------- .../common/payload/PayloadService.java | 60 ++++++ .../common/payload/TypeToClassConverter.java | 71 +++++++ .../AsyncAnnotationOperationsScanner.java | 3 + ...AnnotationClassLevelOperationsScanner.java | 6 +- ...nnotationMethodLevelOperationsScanner.java | 15 +- .../SpringwolfAutoConfiguration.java | 21 +- .../SpringwolfScannerConfiguration.java | 9 + .../AsyncAnnotationChannelsScannerTest.java | 8 +- ...ssLevelChannelsScannerIntegrationTest.java | 8 +- ...notationClassLevelChannelsScannerTest.java | 10 +- ...odLevelChannelsScannerIntegrationTest.java | 8 +- ...otationMethodLevelChannelsScannerTest.java | 20 +- .../payload/PayloadClassExtractorTest.java | 102 ++-------- .../common/payload/PayloadServiceTest.java | 110 ++++++++++ .../payload/TypeToClassConverterTest.java | 129 ++++++++++++ .../AsyncAnnotationOperationsScannerTest.java | 9 +- ...tationClassLevelOperationsScannerTest.java | 10 +- .../AsyncApiDocumentIntegrationTest.java | 6 +- .../consumers/NoPayloadUsedConsumer.java | 18 ++ .../examples/kafka/consumers/XmlConsumer.java | 6 +- .../kafka/consumers/YamlConsumer.java | 6 +- .../kafka/SpringContextIntegrationTest.java | 2 +- .../src/test/resources/asyncapi.json | 189 +++++++++++++----- .../headers/AsyncHeadersForAmqpBuilder.java | 3 +- .../SpringwolfAmqpScannerConfiguration.java | 18 +- ...pProducerConfigurationIntegrationTest.java | 6 + .../CloudStreamFunctionChannelsScanner.java | 5 +- .../common/FunctionalChannelBeanBuilder.java | 6 +- ...pringwolfCloudStreamAutoConfiguration.java | 6 +- ...unctionChannelsScannerIntegrationTest.java | 18 +- .../FunctionalChannelBeanBuilderTest.java | 4 +- .../SpringwolfJmsScannerConfiguration.java | 10 +- ...sProducerConfigurationIntegrationTest.java | 6 + ...pringwolfJmsControllerIntegrationTest.java | 3 +- .../header/AsyncHeadersForKafkaBuilder.java | 8 +- .../SpringwolfKafkaScannerConfiguration.java | 18 +- ...aProducerConfigurationIntegrationTest.java | 8 +- ...ingwolfKafkaControllerIntegrationTest.java | 3 +- ...sProducerConfigurationIntegrationTest.java | 3 + .../SpringwolfSqsScannerConfiguration.java | 10 +- ...sProducerConfigurationIntegrationTest.java | 6 + 60 files changed, 935 insertions(+), 379 deletions(-) create mode 100644 springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java create mode 100644 springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java create mode 100644 springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java create mode 100644 springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadServiceTest.java create mode 100644 springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverterTest.java create mode 100644 springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/NoPayloadUsedConsumer.java diff --git a/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverter.java b/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverter.java index 90142f8d1..e79f2464c 100644 --- a/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverter.java +++ b/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverter.java @@ -14,12 +14,32 @@ public class MonetaryAmountConverter implements ModelConverter { @Override public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator chain) { - JavaType javaType = Json.mapper().constructType(type.getType()); - if (javaType != null) { - Class cls = javaType.getRawClass(); - if (javax.money.MonetaryAmount.class.isAssignableFrom(cls)) - type = new AnnotatedType(MonetaryAmount.class).resolveAsRef(true); + Class rawClass = getRawClass(type); + boolean isMonetaryAmount = isMonetaryAmountType(rawClass); + + if (isMonetaryAmount) { + type = new AnnotatedType(MonetaryAmount.class).resolveAsRef(true); } + + Schema schema = proceedWithChain(type, context, chain); + + if (isMonetaryAmount && schema != null && rawClass != null) { + schema.name(rawClass.getName()); + } + + return schema; + } + + private Class getRawClass(AnnotatedType type) { + JavaType javaType = Json.mapper().constructType(type.getType()); + return javaType != null ? javaType.getRawClass() : null; + } + + private Schema proceedWithChain(AnnotatedType type, ModelConverterContext context, Iterator chain) { return (chain.hasNext()) ? chain.next().resolve(type, context, chain) : null; } + + private boolean isMonetaryAmountType(Class rawClass) { + return javax.money.MonetaryAmount.class.isAssignableFrom(rawClass); + } } diff --git a/springwolf-add-ons/springwolf-common-model-converters/src/test/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverterTest.java b/springwolf-add-ons/springwolf-common-model-converters/src/test/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverterTest.java index 57f203db6..16e21bf63 100644 --- a/springwolf-add-ons/springwolf-common-model-converters/src/test/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverterTest.java +++ b/springwolf-add-ons/springwolf-common-model-converters/src/test/java/io/github/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmountConverterTest.java @@ -6,7 +6,7 @@ import io.swagger.v3.oas.models.media.Schema; import org.junit.jupiter.api.Test; -import javax.money.MonetaryAmount; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -14,18 +14,22 @@ class MonetaryAmountConverterTest { - private MonetaryAmountConverter modelsConverter = new MonetaryAmountConverter(); + private final MonetaryAmountConverter modelsConverter = new MonetaryAmountConverter(); - private ModelConverters converters = new ModelConverters(); + private final ModelConverters converters = new ModelConverters(); @Test void testMonetaryAmountConverter() { - + // given converters.addConverter(modelsConverter); - final Schema model = - converters.readAll(new AnnotatedType(MonetaryAmount.class)).get("MonetaryAmount"); + // when + final Map models = converters.readAll(new AnnotatedType(javax.money.MonetaryAmount.class)); + + // then + final Schema model = models.get("MonetaryAmount"); assertNotNull(model); + assertEquals(2, model.getProperties().size()); final Schema amountProperty = (Schema) model.getProperties().get("amount"); @@ -36,5 +40,12 @@ void testMonetaryAmountConverter() { final Schema missingProperty = (Schema) model.getProperties().get("missing"); assertNull(missingProperty); + + // then + final Schema originalModel = models.get("javax.money.MonetaryAmount"); + assertNotNull(originalModel); + + assertNull(originalModel.getType()); + assertEquals("#/components/schemas/MonetaryAmount", originalModel.get$ref()); } } diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java index 9ccf6e1cb..e63020056 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java @@ -7,10 +7,12 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @JsonSerialize(using = MessagePayloadSerializer.class) @EqualsAndHashCode +@ToString public class MessagePayload { private MultiFormatSchema multiFormatSchema; private SchemaObject schema; diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java index b938b4d48..bc6b16b9d 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/channel/message/MessageReference.java @@ -9,6 +9,7 @@ import lombok.ToString; @EqualsAndHashCode +@ToString @NoArgsConstructor @AllArgsConstructor public class MessageReference implements Message, Reference { diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java index 710a3ed03..497fb78e8 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaObject.java @@ -10,6 +10,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import java.math.BigDecimal; import java.util.List; @@ -31,6 +32,7 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = true) +@ToString public class SchemaObject extends ExtendableObject implements Schema { /** * Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java index 2384a1535..848c27fbd 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/schema/SchemaReference.java @@ -9,6 +9,7 @@ import lombok.ToString; @EqualsAndHashCode +@ToString @NoArgsConstructor @AllArgsConstructor public class SchemaReference implements Schema, Reference { diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java index 9c982b3d6..f51322574 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/ComponentsService.java @@ -12,6 +12,8 @@ public interface ComponentsService { Map getSchemas(); + SchemaObject resolveSchema(String schemaName); + String registerSchema(SchemaObject headers); String registerSchema(Class type); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java index 59caf4e59..b1ada9e67 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java @@ -14,6 +14,7 @@ import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; +import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -60,6 +61,15 @@ public Map getSchemas() { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + @Override + @Nullable + public SchemaObject resolveSchema(String schemaName) { + if (schemas.containsKey(schemaName)) { + return swaggerSchemaUtil.mapSchema(schemas.get(schemaName)); + } + return null; + } + @Override public String registerSchema(SchemaObject headers) { log.debug("Registering schema for {}", headers.getTitle()); @@ -81,6 +91,7 @@ public String registerSchema(SchemaObject headers) { @Override public String registerSchema(Class type) { + // FIXME: Move this to the new HeadersService return this.registerSchema(type, properties.getDocket().getDefaultContentType()); } @@ -90,13 +101,13 @@ public String registerSchema(Class type, String contentType) { String actualContentType = StringUtils.isBlank(contentType) ? properties.getDocket().getDefaultContentType() : contentType; - Map schemas = new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type))); + Map newSchemas = new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type))); - String schemaName = getSchemaName(type, schemas); + String schemaName = getSchemaName(type, newSchemas); - preProcessSchemas(schemas, schemaName, type); - schemas.forEach(this.schemas::putIfAbsent); - schemas.values().forEach(schema -> postProcessSchema(schema, actualContentType)); + preProcessSchemas(newSchemas, schemaName, type); + newSchemas.forEach(this.schemas::putIfAbsent); + newSchemas.values().forEach(schema -> postProcessSchema(schema, actualContentType)); return schemaName; } @@ -130,11 +141,40 @@ private String getSchemaName(Class type, Map schemas) { return new ArrayList<>(resolvedPayloadModelName).get(0); } - return type.getSimpleName(); + return getNameFromClass(type); } private void preProcessSchemas(Map schemas, String schemaName, Class type) { + processCommonModelConverters(schemas); processAsyncApiPayloadAnnotation(schemas, schemaName, type); + processSchemaAnnotation(schemas, schemaName, type); + } + + private void processCommonModelConverters(Map schemas) { + schemas.values().stream() + .filter(schema -> schema.getType() == null) + .filter(schema -> schema.get$ref() != null) + .forEach(schema -> { + String targetSchemaName = schema.getName(); + String sourceSchemaName = StringUtils.substringAfterLast(schema.get$ref(), "/"); + + Schema actualSchema = schemas.get(sourceSchemaName); + + if (actualSchema != null) { + schemas.put(targetSchemaName, actualSchema); + schemas.remove(sourceSchemaName); + } + }); + } + + private void processSchemaAnnotation(Map schemas, String schemaName, Class type) { + Schema schemaForType = schemas.get(schemaName); + if (schemaForType != null) { + var schemaAnnotation = type.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); + if (schemaAnnotation != null) { + schemaForType.setDescription(schemaAnnotation.description()); + } + } } private void processAsyncApiPayloadAnnotation(Map schemas, String schemaName, Class type) { @@ -161,9 +201,9 @@ private void processAsyncApiPayloadAnnotation(Map schemas, Strin } private String registerString() { - String schemaName = "String"; + String schemaName = getNameFromClass(String.class); StringSchema schema = new StringSchema(); - schema.setName(String.class.getName()); + schema.setName(schemaName); this.schemas.put(schemaName, schema); postProcessSchema(schema, DEFAULT_CONTENT_TYPE); @@ -181,6 +221,13 @@ private R runWithFqnSetting(Function callable) { return result; } + private String getNameFromClass(Class type) { + if (properties.isUseFqn()) { + return type.getName(); + } + return type.getSimpleName(); + } + private void postProcessSchema(Schema schema, String contentType) { for (SchemasPostProcessor processor : schemaPostProcessors) { processor.process(schema, schemas, contentType); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java index 195fcb136..0496125f6 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersBuilder.java @@ -2,7 +2,8 @@ package io.github.springwolf.core.asyncapi.components.headers; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; public interface AsyncHeadersBuilder { - SchemaObject buildHeaders(Class payloadType); + SchemaObject buildHeaders(NamedSchemaObject payloadSchema); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java index b1548c4e2..77b177bfc 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotDocumented.java @@ -2,6 +2,7 @@ package io.github.springwolf.core.asyncapi.components.headers; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import java.util.List; import java.util.Map; @@ -22,7 +23,7 @@ public class AsyncHeadersNotDocumented implements AsyncHeadersBuilder { } @Override - public SchemaObject buildHeaders(Class payloadType) { + public SchemaObject buildHeaders(NamedSchemaObject payloadSchema) { return NOT_DOCUMENTED; } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java index 19ffed265..8b026f07b 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/headers/AsyncHeadersNotUsed.java @@ -2,6 +2,7 @@ package io.github.springwolf.core.asyncapi.components.headers; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import java.util.List; import java.util.Map; @@ -21,7 +22,7 @@ public class AsyncHeadersNotUsed implements AsyncHeadersBuilder { } @Override - public SchemaObject buildHeaders(Class payloadType) { + public SchemaObject buildHeaders(NamedSchemaObject payloadSchema) { return NOT_USED; } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java index 253e1c7cd..7c28ddab3 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/AsyncAnnotationChannelsScanner.java @@ -15,6 +15,7 @@ import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AsyncAnnotationUtil; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import lombok.extern.slf4j.Slf4j; @@ -36,11 +37,13 @@ public AsyncAnnotationChannelsScanner( ComponentsService componentsService, AsyncApiDocketService asyncApiDocketService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { super( asyncAnnotationProvider, payloadClassExtractor, + payloadService, componentsService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java index d86fff299..22d882262 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java @@ -8,7 +8,7 @@ import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.ClassLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -30,14 +30,14 @@ public SpringAnnotationClassLevelChannelsScanner( Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super( classAnnotationClass, methodAnnotationClass, bindingFactory, asyncHeadersBuilder, - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java index c29421473..2321fa214 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java @@ -9,7 +9,8 @@ import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.MethodLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -25,17 +26,17 @@ public class SpringAnnotationMethodLevelChannelsScanner implements SpringAnnotationChannelsScannerDelegator { private final Class methodAnnotationClass; - private final PayloadClassExtractor payloadClassExtractor; + private final PayloadService payloadService; public SpringAnnotationMethodLevelChannelsScanner( Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super(bindingFactory, asyncHeadersBuilder, componentsService); this.methodAnnotationClass = methodAnnotationClass; - this.payloadClassExtractor = payloadClassExtractor; + this.payloadService = payloadService; } @Override @@ -56,16 +57,15 @@ private Map.Entry mapMethodToChannel(Method method) { MethodAnnotation annotation = AnnotationScannerUtil.findAnnotationOrThrow(methodAnnotationClass, method); - String channelName = bindingFactory.getChannelName(annotation); - Class payload = payloadClassExtractor.extractFrom(method); - - ChannelObject channelItem = buildChannelItem(annotation, payload); + NamedSchemaObject payloadSchema = payloadService.extractSchema(method); + ChannelObject channelItem = buildChannelItem(annotation, payloadSchema); + String channelName = bindingFactory.getChannelName(annotation); return Map.entry(channelName, channelItem); } - private ChannelObject buildChannelItem(MethodAnnotation annotation, Class payloadType) { - MessageObject message = buildMessage(annotation, payloadType); + private ChannelObject buildChannelItem(MethodAnnotation annotation, NamedSchemaObject payloadSchema) { + MessageObject message = buildMessage(annotation, payloadSchema); return buildChannelItem(annotation, message); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java index 0dbba50dc..02c1b702f 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/AsyncAnnotationScanner.java @@ -17,11 +17,12 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.bindings.messages.MessageBindingProcessor; import io.github.springwolf.core.asyncapi.scanners.bindings.operations.OperationBindingProcessor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.common.utils.AsyncAnnotationUtil; import io.github.springwolf.core.asyncapi.scanners.common.utils.TextUtils; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.EmbeddedValueResolverAware; @@ -42,6 +43,7 @@ public abstract class AsyncAnnotationScanner implements Em protected final AsyncAnnotationProvider asyncAnnotationProvider; protected final PayloadClassExtractor payloadClassExtractor; + protected final PayloadService payloadService; protected final ComponentsService componentsService; protected final List operationBindingProcessors; protected final List messageBindingProcessors; @@ -89,30 +91,33 @@ protected Operation buildOperation(AsyncOperation asyncOperation, Method method, } protected MessageObject buildMessage(AsyncOperation operationData, Method method) { - Class payloadType = operationData.payloadType() != Object.class - ? operationData.payloadType() - : payloadClassExtractor.extractFrom(method); + NamedSchemaObject payloadSchema = payloadService.extractSchema(operationData, method); - String modelName = this.componentsService.registerSchema( - payloadType, operationData.message().contentType()); - SchemaObject asyncHeaders = AsyncAnnotationUtil.getAsyncHeaders(operationData, resolver); - String headerModelName = this.componentsService.registerSchema(asyncHeaders); - var headers = MessageHeaders.of(MessageReference.toSchema(headerModelName)); - - var schema = payloadType.getAnnotation(Schema.class); - String description = schema != null ? schema.description() : null; + // TODO: move block to own HeaderService + SchemaObject headerSchema = AsyncAnnotationUtil.getAsyncHeaders(operationData, resolver); + String headerSchemaName = this.componentsService.registerSchema(headerSchema); + var headers = MessageHeaders.of(MessageReference.toSchema(headerSchemaName)); Map messageBinding = AsyncAnnotationUtil.processMessageBindingFromAnnotation(method, messageBindingProcessors); var messagePayload = MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(modelName)) + .schema(SchemaReference.fromSchema(payloadSchema.name())) .build()); + String description = operationData.message().description(); + if (!StringUtils.hasText(description)) { + description = payloadSchema.schema().getDescription(); + } + if (StringUtils.hasText(description)) { + description = this.resolver.resolveStringValue(description); + description = TextUtils.trimIndent(description); + } + var builder = MessageObject.builder() - .messageId(payloadType.getName()) - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) + .messageId(payloadSchema.name()) + .name(payloadSchema.name()) + .title(payloadSchema.schema().getTitle()) .description(description) .payload(messagePayload) .headers(headers) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java index f814e9392..8a7f6507d 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java @@ -11,7 +11,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner; import lombok.RequiredArgsConstructor; @@ -37,7 +38,7 @@ public abstract class ClassLevelAnnotationScanner< protected final Class methodAnnotationClass; protected final BindingFactory bindingFactory; protected final AsyncHeadersBuilder asyncHeadersBuilder; - protected final PayloadClassExtractor payloadClassExtractor; + protected final PayloadService payloadService; protected final ComponentsService componentsService; protected enum MessageType { @@ -67,8 +68,8 @@ protected Map buildMessages( SpringAnnotationClassLevelOperationsScanner.MessageType messageType) { Set messages = methods.stream() .map((Method method) -> { - Class payloadType = payloadClassExtractor.extractFrom(method); - return buildMessage(classAnnotation, payloadType); + NamedSchemaObject payloadSchema = payloadService.extractSchema(method); + return buildMessage(classAnnotation, payloadSchema); }) .collect(toSet()); @@ -79,18 +80,19 @@ protected Map buildMessages( return toMessagesMap(messages); } - protected MessageObject buildMessage(ClassAnnotation classAnnotation, Class payloadType) { + protected MessageObject buildMessage(ClassAnnotation classAnnotation, NamedSchemaObject payloadSchema) { Map messageBinding = bindingFactory.buildMessageBinding(classAnnotation); - String modelName = componentsService.registerSchema(payloadType); - String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType)); + + String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadSchema)); + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(modelName)) + .schema(SchemaReference.fromSchema(payloadSchema.name())) .build()); MessageObject message = MessageObject.builder() - .messageId(payloadType.getName()) - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) + .messageId(payloadSchema.name()) + .name(payloadSchema.name()) + .title(payloadSchema.schema().getTitle()) .description(null) .payload(payload) .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java index 1d5481223..72f5e8237 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MethodLevelAnnotationScanner.java @@ -11,6 +11,7 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,18 +26,20 @@ public abstract class MethodLevelAnnotationScanner payloadType) { + protected MessageObject buildMessage(MethodAnnotation annotation, NamedSchemaObject payloadSchema) { Map messageBinding = bindingFactory.buildMessageBinding(annotation); - String modelName = componentsService.registerSchema(payloadType); - String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType)); + + // TODO: move block to own HeaderService + String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadSchema)); + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(modelName)) + .schema(SchemaReference.fromSchema(payloadSchema.name())) .build()); MessageObject message = MessageObject.builder() - .messageId(payloadType.getName()) - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) + .messageId(payloadSchema.name()) + .name(payloadSchema.name()) + .title(payloadSchema.schema().getTitle()) .description(null) .payload(payload) .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java new file mode 100644 index 000000000..9509becf1 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/NamedSchemaObject.java @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; + +/** + * Encapsulates the resolved name for the contained schema. + * @param name The fully qualified name or the simple name of the schema. + * @param schema The SchemaObject. + */ +public record NamedSchemaObject(String name, SchemaObject schema) {} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java index 0d19c5e69..9e80ef74e 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractor.java @@ -1,52 +1,34 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.core.asyncapi.scanners.common.payload; -import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.handler.annotation.Payload; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.Arrays; -import java.util.Map; +import java.util.Optional; +@RequiredArgsConstructor @Slf4j public class PayloadClassExtractor { private final TypeToClassConverter typeToClassConverter; - public PayloadClassExtractor(SpringwolfConfigProperties properties) { - Map extractableClasses = Map.of(); - if (properties.getPayload() != null) { - extractableClasses = properties.getPayload().getExtractableClasses(); - } - typeToClassConverter = new TypeToClassConverter(extractableClasses); - } - - public Class extractFrom(Method method) { + public Optional> extractFrom(Method method) { String methodName = String.format("%s::%s", method.getDeclaringClass().getSimpleName(), method.getName()); log.debug("Finding payload type for {}", methodName); - int parameterPayloadIndex = - getPayloadParameterIndex(method.getParameterTypes(), method.getParameterAnnotations(), methodName); - - return typeToClassConverter.extractClass(method.getGenericParameterTypes()[parameterPayloadIndex]); + return getPayloadParameterIndex(method.getParameterTypes(), method.getParameterAnnotations(), methodName) + .map((parameterPayloadIndex) -> + typeToClassConverter.extractClass(method.getGenericParameterTypes()[parameterPayloadIndex])); } - public Class typeToClass(Type type) { - return typeToClassConverter.extractClass(type); - } - - private int getPayloadParameterIndex( + private Optional getPayloadParameterIndex( Class[] parameterClasses, Annotation[][] parameterAnnotations, String methodName) { - switch (parameterClasses.length) { - case 0 -> throw new IllegalArgumentException( - "Payload cannot be detected. Method must not have 0 parameters: " + methodName); - case 1 -> { - return 0; - } + return switch (parameterClasses.length) { + case 0 -> Optional.empty(); + case 1 -> Optional.of(0); default -> { int payloadAnnotatedParameterIndex = getPayloadAnnotatedParameterIndex(parameterAnnotations); if (payloadAnnotatedParameterIndex == -1) { @@ -57,9 +39,9 @@ private int getPayloadParameterIndex( throw new IllegalArgumentException(msg); } - return payloadAnnotatedParameterIndex; + yield Optional.of(payloadAnnotatedParameterIndex); } - } + }; } private int getPayloadAnnotatedParameterIndex(Annotation[][] parameterAnnotations) { @@ -74,58 +56,4 @@ private int getPayloadAnnotatedParameterIndex(Annotation[][] parameterAnnotation return -1; } - - @RequiredArgsConstructor - private static class TypeToClassConverter { - - private final Map extractableClassToArgumentIndex; - - private Class extractClass(Type parameterType) { - try { - if (parameterType instanceof ParameterizedType) { - Type rawParameterType = ((ParameterizedType) parameterType).getRawType(); - String rawParameterTypeName = rawParameterType.getTypeName(); - - Class actualPayloadClass = - extractActualGenericClass((ParameterizedType) parameterType, rawParameterTypeName); - if (actualPayloadClass != Void.class) { - return actualPayloadClass; - } - - // nested generic class - fallback to most outer container - return Class.forName(rawParameterTypeName); - } - - // no generics used - just a normal type - return Class.forName(parameterType.getTypeName()); - } catch (Exception ex) { - log.info("Unable to extract generic data type of {}", parameterType, ex); - } - return Void.class; - } - - private Class extractActualGenericClass(ParameterizedType parameterType, String rawParameterTypeName) { - Type type = parameterType; - String typeName = rawParameterTypeName; - - while (type instanceof ParameterizedType && extractableClassToArgumentIndex.containsKey(typeName)) { - Integer index = extractableClassToArgumentIndex.get(rawParameterTypeName); - - type = ((ParameterizedType) type).getActualTypeArguments()[index]; - - typeName = type.getTypeName(); - if (type instanceof ParameterizedType) { - typeName = ((ParameterizedType) type).getRawType().getTypeName(); - } - } - - try { - return Class.forName(typeName); - } catch (ClassNotFoundException ex) { - log.debug("Unable to find class for type {}", typeName, ex); - } - - return Void.class; - } - } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java new file mode 100644 index 000000000..69342a90f --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadService.java @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.annotations.AsyncOperation; +import io.github.springwolf.core.asyncapi.components.ComponentsService; +import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RequiredArgsConstructor +public class PayloadService { + private final PayloadClassExtractor payloadClassExtractor; + private final ComponentsService componentsService; + private final SpringwolfConfigProperties properties; + + private static final String PAYLOAD_NOT_USED_KEY = "PayloadNotUsed"; + static final NamedSchemaObject PAYLOAD_NOT_USED = new NamedSchemaObject( + PAYLOAD_NOT_USED_KEY, + SchemaObject.builder() + .title(PAYLOAD_NOT_USED_KEY) + .description("No payload specified") + .properties(Map.of()) + .build()); + + public NamedSchemaObject extractSchema(Method method) { + Optional> payloadType = payloadClassExtractor.extractFrom(method); + + String contentType = properties.getDocket().getDefaultContentType(); + return payloadType.map((type) -> buildSchema(contentType, type)).orElseGet(this::useUnusedPayload); + } + + public NamedSchemaObject extractSchema(AsyncOperation operationData, Method method) { + Optional> payloadType = operationData.payloadType() != Object.class + ? Optional.of(operationData.payloadType()) + : payloadClassExtractor.extractFrom(method); + + String contentType = operationData.message().contentType(); + return payloadType.map((type) -> buildSchema(contentType, type)).orElseGet(this::useUnusedPayload); + } + + private NamedSchemaObject buildSchema(String contentType, Class payloadType) { + String componentsSchemaName = this.componentsService.registerSchema(payloadType, contentType); + + SchemaObject schema = componentsService.resolveSchema(componentsSchemaName); + schema.setTitle(payloadType.getSimpleName()); + + return new NamedSchemaObject(componentsSchemaName, schema); + } + + private NamedSchemaObject useUnusedPayload() { + this.componentsService.registerSchema(PAYLOAD_NOT_USED.schema()); + return PAYLOAD_NOT_USED; + } +} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java new file mode 100644 index 000000000..50fcdd568 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverter.java @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +@Slf4j +public class TypeToClassConverter { + + private final Map extractableClassToArgumentIndex; + + public TypeToClassConverter(SpringwolfConfigProperties properties) { + if (properties.getPayload() != null) { + extractableClassToArgumentIndex = properties.getPayload().getExtractableClasses(); + } else { + extractableClassToArgumentIndex = Map.of(); + } + } + + public Class extractClass(Type parameterType) { + try { + if (parameterType instanceof ParameterizedType) { + Type rawParameterType = ((ParameterizedType) parameterType).getRawType(); + String rawParameterTypeName = rawParameterType.getTypeName(); + + Class actualPayloadClass = + extractActualGenericClass((ParameterizedType) parameterType, rawParameterTypeName); + if (actualPayloadClass != Void.class) { + return actualPayloadClass; + } + + // nested generic class - fallback to most outer container + return Class.forName(rawParameterTypeName); + } + + // no generics used - just a normal type + return Class.forName(parameterType.getTypeName()); + } catch (Exception ex) { + log.info("Unable to extract generic data type of {}", parameterType, ex); + } + return Void.class; + } + + private Class extractActualGenericClass(ParameterizedType parameterType, String rawParameterTypeName) { + Type type = parameterType; + String typeName = rawParameterTypeName; + + while (type instanceof ParameterizedType && extractableClassToArgumentIndex.containsKey(typeName)) { + Integer index = extractableClassToArgumentIndex.get(rawParameterTypeName); + + type = ((ParameterizedType) type).getActualTypeArguments()[index]; + + typeName = type.getTypeName(); + if (type instanceof ParameterizedType) { + typeName = ((ParameterizedType) type).getRawType().getTypeName(); + } + } + + try { + return Class.forName(typeName); + } catch (ClassNotFoundException ex) { + log.debug("Unable to find class for type {}", typeName, ex); + } + + return Void.class; + } +} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java index 6cee589b0..16ac62046 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScanner.java @@ -10,6 +10,7 @@ import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.OperationMerger; import lombok.extern.slf4j.Slf4j; @@ -28,11 +29,13 @@ public AsyncAnnotationOperationsScanner( ClassScanner classScanner, ComponentsService componentsService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { super( asyncAnnotationProvider, payloadClassExtractor, + payloadService, componentsService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java index 74b0db0c8..811669507 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java @@ -10,7 +10,7 @@ import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.ClassLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -32,14 +32,14 @@ public SpringAnnotationClassLevelOperationsScanner( Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super( classAnnotationClass, methodAnnotationClass, bindingFactory, asyncHeadersBuilder, - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java index e222893fb..ac5448ac2 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java @@ -11,7 +11,8 @@ import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.MethodLevelAnnotationScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil; import lombok.extern.slf4j.Slf4j; @@ -28,17 +29,17 @@ public class SpringAnnotationMethodLevelOperationsScanner implements SpringAnnotationOperationsScannerDelegator { private final Class methodAnnotationClass; - private final PayloadClassExtractor payloadClassExtractor; + private final PayloadService payloadService; public SpringAnnotationMethodLevelOperationsScanner( Class methodAnnotationClass, BindingFactory bindingFactory, AsyncHeadersBuilder asyncHeadersBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { super(bindingFactory, asyncHeadersBuilder, componentsService); this.methodAnnotationClass = methodAnnotationClass; - this.payloadClassExtractor = payloadClassExtractor; + this.payloadService = payloadService; } @Override @@ -61,13 +62,13 @@ private Map.Entry mapMethodToOperation(Method method) { String channelName = bindingFactory.getChannelName(annotation); String operationId = channelName + "_" + OperationAction.RECEIVE + "_" + method.getName(); - Class payload = payloadClassExtractor.extractFrom(method); + NamedSchemaObject payloadSchema = payloadService.extractSchema(method); - Operation operation = buildOperation(annotation, payload); + Operation operation = buildOperation(annotation, payloadSchema); return Map.entry(operationId, operation); } - private Operation buildOperation(MethodAnnotation annotation, Class payloadType) { + private Operation buildOperation(MethodAnnotation annotation, NamedSchemaObject payloadType) { MessageObject message = buildMessage(annotation, payloadType); return buildOperation(annotation, message); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java index b8284b26e..2851142c5 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfAutoConfiguration.java @@ -28,6 +28,8 @@ import io.github.springwolf.core.asyncapi.scanners.ChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.OperationsScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.configuration.docket.DefaultAsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants; @@ -161,7 +163,22 @@ public SchemaWalker yamlSchemaWalker(ExampleYamlValueSerializer exampleYamlValue @Bean @ConditionalOnMissingBean - public PayloadClassExtractor payloadClassExtractor(SpringwolfConfigProperties springwolfConfigProperties) { - return new PayloadClassExtractor(springwolfConfigProperties); + public TypeToClassConverter typeToClassConverter(SpringwolfConfigProperties springwolfConfigProperties) { + return new TypeToClassConverter(springwolfConfigProperties); + } + + @Bean + @ConditionalOnMissingBean + public PayloadClassExtractor payloadClassExtractor(TypeToClassConverter typeToClassConverter) { + return new PayloadClassExtractor(typeToClassConverter); + } + + @Bean + @ConditionalOnMissingBean + public PayloadService payloadService( + PayloadClassExtractor payloadClassExtractor, + ComponentsService componentsService, + SpringwolfConfigProperties properties) { + return new PayloadService(payloadClassExtractor, componentsService, properties); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java index a0c9e7d8c..ab416da7f 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfScannerConfiguration.java @@ -17,6 +17,7 @@ import io.github.springwolf.core.asyncapi.scanners.classes.spring.ConfigurationClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.AsyncAnnotationOperationsScanner; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -74,6 +75,7 @@ public AsyncAnnotationChannelsScanner asyncListenerAnnotationChan ComponentsService componentsService, AsyncApiDocketService asyncApiDocketService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationChannelsScanner<>( @@ -82,6 +84,7 @@ public AsyncAnnotationChannelsScanner asyncListenerAnnotationChan componentsService, asyncApiDocketService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } @@ -96,6 +99,7 @@ public AsyncAnnotationOperationsScanner asyncListenerAnnotationOp SpringwolfClassScanner springwolfClassScanner, ComponentsService componentsService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationOperationsScanner<>( @@ -103,6 +107,7 @@ public AsyncAnnotationOperationsScanner asyncListenerAnnotationOp springwolfClassScanner, componentsService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } @@ -118,6 +123,7 @@ public AsyncAnnotationChannelsScanner asyncPublisherChannelAnnot ComponentsService componentsService, AsyncApiDocketService asyncApiDocketService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationChannelsScanner<>( @@ -126,6 +132,7 @@ public AsyncAnnotationChannelsScanner asyncPublisherChannelAnnot componentsService, asyncApiDocketService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } @@ -140,6 +147,7 @@ public AsyncAnnotationOperationsScanner asyncPublisherOperationA SpringwolfClassScanner springwolfClassScanner, ComponentsService componentsService, PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, List operationBindingProcessors, List messageBindingProcessors) { return new AsyncAnnotationOperationsScanner<>( @@ -147,6 +155,7 @@ public AsyncAnnotationOperationsScanner asyncPublisherOperationA springwolfClassScanner, componentsService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java index 39b2002da..7e4bdb5c0 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java @@ -28,6 +28,8 @@ import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocket; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; @@ -85,7 +87,10 @@ public OperationAction getOperationType() { new DefaultComponentsService(emptyList(), emptyList(), swaggerSchemaUtil, properties); private final AsyncApiDocketService asyncApiDocketService = mock(AsyncApiDocketService.class); - private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(properties); + private final TypeToClassConverter typeToClassConverter = new TypeToClassConverter(properties); + private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(typeToClassConverter); + private final PayloadService payloadService = + new PayloadService(payloadClassExtractor, componentsService, properties); private final List operationBindingProcessors = List.of(new TestOperationBindingProcessor()); @@ -99,6 +104,7 @@ public OperationAction getOperationType() { componentsService, asyncApiDocketService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java index 11e2dec6a..3671c992c 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerIntegrationTest.java @@ -20,6 +20,8 @@ import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import lombok.Data; import lombok.EqualsAndHashCode; @@ -48,7 +50,9 @@ SpringAnnotationClassLevelChannelsScannerIntegrationTest.TestBindingFactory.class, DefaultComponentsService.class, SwaggerSchemaUtil.class, + PayloadService.class, PayloadClassExtractor.class, + TypeToClassConverter.class, DefaultSchemaWalker.class, SchemaWalkerProvider.class, ExampleJsonValueGenerator.class, @@ -60,7 +64,7 @@ class SpringAnnotationClassLevelChannelsScannerIntegrationTest { BindingFactory bindingFactory; @Autowired - PayloadClassExtractor payloadClassExtractor; + PayloadService payloadService; @Autowired ComponentsService componentsService; @@ -74,7 +78,7 @@ void setUp() { TestMethodListener.class, this.bindingFactory, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java index 9f0ba78b2..601cf424c 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java @@ -18,7 +18,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.jupiter.api.BeforeEach; @@ -38,7 +39,7 @@ class SpringAnnotationClassLevelChannelsScannerTest { - private final PayloadClassExtractor payloadClassExtractor = mock(PayloadClassExtractor.class); + private final PayloadService payloadService = mock(); private final BindingFactory bindingFactory = mock(BindingFactory.class); private final ComponentsService componentsService = mock(ComponentsService.class); SpringAnnotationClassLevelChannelsScanner scanner = @@ -47,7 +48,7 @@ class SpringAnnotationClassLevelChannelsScannerTest { TestMethodListener.class, bindingFactory, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); private static final String CHANNEL = "test-channel"; @@ -67,7 +68,8 @@ void setUp() { doReturn(defaultChannelBinding).when(bindingFactory).buildChannelBinding(any()); doReturn(defaultMessageBinding).when(bindingFactory).buildMessageBinding(any()); - doReturn(String.class).when(payloadClassExtractor).extractFrom(any()); + when(payloadService.extractSchema(any())) + .thenReturn(new NamedSchemaObject(String.class.getName(), new SchemaObject())); doAnswer(invocation -> invocation.>getArgument(0).getSimpleName()) .when(componentsService) .registerSchema(any(Class.class)); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java index dcc717171..2446885bb 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerIntegrationTest.java @@ -20,6 +20,8 @@ import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import lombok.Data; import lombok.EqualsAndHashCode; @@ -50,7 +52,9 @@ SpringAnnotationMethodLevelChannelsScannerIntegrationTest.TestBindingFactory.class, DefaultComponentsService.class, SwaggerSchemaUtil.class, + PayloadService.class, PayloadClassExtractor.class, + TypeToClassConverter.class, DefaultSchemaWalker.class, SchemaWalkerProvider.class, ExampleJsonValueGenerator.class, @@ -61,7 +65,7 @@ class SpringAnnotationMethodLevelChannelsScannerIntegrationTest { BindingFactory bindingFactory; @Autowired - PayloadClassExtractor payloadClassExtractor; + PayloadService payloadService; @Autowired ComponentsService componentsService; @@ -74,7 +78,7 @@ void setUp() { TestChannelListener.class, this.bindingFactory, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java index b8f2f756f..3abd046c0 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java @@ -18,7 +18,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.jupiter.api.BeforeEach; @@ -39,15 +40,11 @@ class SpringAnnotationMethodLevelChannelsScannerTest { - private final PayloadClassExtractor payloadClassExtractor = mock(PayloadClassExtractor.class); + private final PayloadService payloadService = mock(); private final BindingFactory bindingFactory = mock(BindingFactory.class); private final ComponentsService componentsService = mock(ComponentsService.class); SpringAnnotationMethodLevelChannelsScanner scanner = new SpringAnnotationMethodLevelChannelsScanner<>( - TestListener.class, - bindingFactory, - new AsyncHeadersNotDocumented(), - payloadClassExtractor, - componentsService); + TestListener.class, bindingFactory, new AsyncHeadersNotDocumented(), payloadService, componentsService); private static final String CHANNEL = "test-channel"; private static final Map defaultOperationBinding = @@ -66,7 +63,8 @@ void setUp() throws NoSuchMethodException { doReturn(defaultChannelBinding).when(bindingFactory).buildChannelBinding(any()); doReturn(defaultMessageBinding).when(bindingFactory).buildMessageBinding(any()); - doReturn(String.class).when(payloadClassExtractor).extractFrom(any()); + when(payloadService.extractSchema(any())) + .thenReturn(new NamedSchemaObject(String.class.getName(), new SchemaObject())); doAnswer(invocation -> invocation.>getArgument(0).getSimpleName()) .when(componentsService) .registerSchema(any(Class.class)); @@ -76,10 +74,12 @@ void setUp() throws NoSuchMethodException { var stringMethod = ClassWithMultipleTestListenerAnnotation.class.getDeclaredMethod("methodWithAnnotation", String.class); - doReturn(String.class).when(payloadClassExtractor).extractFrom(stringMethod); + when(payloadService.extractSchema(stringMethod)) + .thenReturn(new NamedSchemaObject(String.class.getName(), new SchemaObject())); var simpleFooMethod = ClassWithMultipleTestListenerAnnotation.class.getDeclaredMethod( "anotherMethodWithAnnotation", SimpleFoo.class); - doReturn(SimpleFoo.class).when(payloadClassExtractor).extractFrom(simpleFooMethod); + when(payloadService.extractSchema(simpleFooMethod)) + .thenReturn(new NamedSchemaObject(SimpleFoo.class.getName(), new SchemaObject())); } @Test diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractorTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractorTest.java index 689d60726..a142556e5 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractorTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadClassExtractorTest.java @@ -1,46 +1,46 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.core.asyncapi.scanners.common.payload; -import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.messaging.Message; import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.messaging.support.GenericMessage; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; class PayloadClassExtractorTest { - private final PayloadClassExtractor extractor; + private final TypeToClassConverter typeToClassConverter = mock(TypeToClassConverter.class); - { - SpringwolfConfigProperties properties = new SpringwolfConfigProperties(); - properties.setPayload(new SpringwolfConfigProperties.Payload()); - extractor = new PayloadClassExtractor(properties); + private final PayloadClassExtractor extractor = new PayloadClassExtractor(typeToClassConverter); + + @BeforeEach + void setUp() { + doReturn(String.class).when(typeToClassConverter).extractClass(any()); } @Test void getPayloadType() throws NoSuchMethodException { Method m = TestClass.class.getDeclaredMethod("consumeWithString", String.class); - Class result = extractor.extractFrom(m); + Optional> result = extractor.extractFrom(m); - assertEquals(String.class, result); + assertThat(result).hasValue(String.class); } @Test void getPayloadTypeWithPayloadAnnotation() throws NoSuchMethodException { Method m = TestClass.class.getDeclaredMethod("consumeWithPayloadAnnotation", String.class, Integer.class); - Class result = extractor.extractFrom(m); + Optional> result = extractor.extractFrom(m); - assertEquals(String.class, result); + assertThat(result).hasValue(String.class); } @Test @@ -57,58 +57,12 @@ void getPayloadTypeWithoutPayloadAnnotation() throws NoSuchMethodException { } @Test - void getPayloadTypeWithMessageOfInterfaces() throws NoSuchMethodException { - Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfGenericClasses", Message.class); + void getNoPayload() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithoutPayload"); - Class result = extractor.extractFrom(m); + Optional> result = extractor.extractFrom(m); - assertEquals(Collection.class, result); - } - - @Test - void getPayloadTypeWithInterface() throws NoSuchMethodException { - Method m = TestClass.class.getDeclaredMethod("consumeWithGenericClass", Collection.class); - - Class result = extractor.extractFrom(m); - - assertEquals(Collection.class, result); - } - - @Test - void getPayloadTypeWithMessageOfStringExtends() throws NoSuchMethodException { - Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfStringExtends", Message.class); - - Class result = extractor.extractFrom(m); - - // Unable to resolve optional, fallback to root type Message - assertEquals(Message.class, result); - } - - @Test - void getPayloadTypeWithMessageOfListOfString() throws NoSuchMethodException { - Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfListOfString", Message.class); - - Class result = extractor.extractFrom(m); - - assertEquals(String.class, result); - } - - @Test - void getPayloadTypeWithMessageOfString() throws NoSuchMethodException { - Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfString", Message.class); - - Class result = extractor.extractFrom(m); - - assertEquals(String.class, result); - } - - @Test - void getPayloadTypeWithCustomType() throws NoSuchMethodException { - Method m = TestClass.class.getDeclaredMethod("consumeWithCustomType", TestClass.MyType.class); - - Class result = extractor.extractFrom(m); - - assertEquals(TestClass.MyType.class, result); + assertThat(result).isEmpty(); } public static class TestClass { @@ -118,22 +72,6 @@ public void consumeWithoutPayloadAnnotation(String value, Integer value2) {} public void consumeWithString(String value) {} - public void consumeWithGenericClass(Collection value) {} - - public void consumeWithMessageOfGenericClasses(Message> value) {} - - public void consumeWithMessageOfStringExtends(Message value) {} - - public void consumeWithMessageOfListOfString(Message> value) {} - - public void consumeWithMessageOfString(Message value) {} - - public void consumeWithCustomType(MyType value) {} - - public static class MyType extends GenericMessage { - public MyType(String payload) { - super(payload); - } - } + public void consumeWithoutPayload() {} } } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadServiceTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadServiceTest.java new file mode 100644 index 000000000..42b5299f1 --- /dev/null +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/PayloadServiceTest.java @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.springwolf.core.asyncapi.annotations.AsyncMessage; +import io.github.springwolf.core.asyncapi.annotations.AsyncOperation; +import io.github.springwolf.core.asyncapi.components.ComponentsService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Method; +import java.util.Optional; + +import static io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService.PAYLOAD_NOT_USED; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PayloadServiceTest { + + @Mock + private PayloadClassExtractor payloadClassExtractor; + + @Mock + private ComponentsService componentsService; + + @InjectMocks + private PayloadService payloadService; + + @Test + public void shouldUsePayloadFromAsyncOperationAnnotation() { + // given + AsyncMessage asyncMessage = mock(AsyncMessage.class); + when(asyncMessage.contentType()).thenReturn("application/json"); + + AsyncOperation asyncOperation = mock(AsyncOperation.class); + doReturn(String.class).when(asyncOperation).payloadType(); + when(asyncOperation.message()).thenReturn(asyncMessage); + + String schemaName = "my-schema-name"; + when(componentsService.registerSchema(any(), any())).thenReturn(schemaName); + + SchemaObject schemaObject = SchemaObject.builder().build(); + when(componentsService.resolveSchema(schemaName)).thenReturn(schemaObject); + + // when + var result = payloadService.extractSchema(asyncOperation, null); + + // then + assertThat(result.name()).isEqualTo(schemaName); + assertThat(result.schema()).isEqualTo(schemaObject); + } + + @Test + public void shouldExtractPayloadFromMethod() { + // given + AsyncMessage asyncMessage = mock(AsyncMessage.class); + when(asyncMessage.contentType()).thenReturn("application/json"); + + AsyncOperation asyncOperation = mock(AsyncOperation.class); + doReturn(Object.class).when(asyncOperation).payloadType(); + when(asyncOperation.message()).thenReturn(asyncMessage); + + Method method = mock(Method.class); + when(payloadClassExtractor.extractFrom(method)).thenReturn(Optional.of(String.class)); + + String schemaName = "my-schema-name"; + when(componentsService.registerSchema(any(), any())).thenReturn(schemaName); + + SchemaObject schemaObject = SchemaObject.builder().build(); + when(componentsService.resolveSchema(schemaName)).thenReturn(schemaObject); + + // when + var result = payloadService.extractSchema(asyncOperation, method); + + // then + assertThat(result.name()).isEqualTo(schemaName); + assertThat(result.schema()).isEqualTo(schemaObject); + } + + @Test + public void shouldReturnPayloadNotUsed() { + // given + AsyncMessage asyncMessage = mock(AsyncMessage.class); + when(asyncMessage.contentType()).thenReturn("application/json"); + + AsyncOperation asyncOperation = mock(AsyncOperation.class); + doReturn(Object.class).when(asyncOperation).payloadType(); + when(asyncOperation.message()).thenReturn(asyncMessage); + + Method method = mock(Method.class); + when(payloadClassExtractor.extractFrom(method)).thenReturn(Optional.empty()); + + // when + var result = payloadService.extractSchema(asyncOperation, method); + + // then + assertThat(result.name()).isEqualTo("PayloadNotUsed"); + assertThat(result.schema().getTitle()).isEqualTo("PayloadNotUsed"); + assertThat(result.schema().getDescription()).isEqualTo("No payload specified"); + verify(componentsService).registerSchema(PAYLOAD_NOT_USED.schema()); + } +} diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverterTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverterTest.java new file mode 100644 index 000000000..dba6ee821 --- /dev/null +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/payload/TypeToClassConverterTest.java @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.asyncapi.scanners.common.payload; + +import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import org.junit.jupiter.api.Test; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.GenericMessage; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TypeToClassConverterTest { + + private final TypeToClassConverter typeToClassConverter; + + { + SpringwolfConfigProperties properties = new SpringwolfConfigProperties(); + properties.setPayload(new SpringwolfConfigProperties.Payload()); + typeToClassConverter = new TypeToClassConverter(properties); + } + + @Test + void getPayloadType() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithString", String.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + assertThat(result).isEqualTo(String.class); + } + + @Test + void getPayloadTypeWithMessageOfInterfaces() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfGenericClasses", Message.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + assertThat(result).isEqualTo(Collection.class); + } + + @Test + void getPayloadTypeWithInterface() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithGenericClass", Collection.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + assertThat(result).isEqualTo(Collection.class); + } + + @Test + void getPayloadTypeWithMessageOfStringExtends() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfStringExtends", Message.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + // Unable to resolve optional, fallback to root type Message + assertThat(result).isEqualTo(Message.class); + } + + @Test + void getPayloadTypeWithMessageOfListOfString() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfListOfString", Message.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + assertThat(result).isEqualTo(String.class); + } + + @Test + void getPayloadTypeWithMessageOfString() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithMessageOfString", Message.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + assertThat(result).isEqualTo(String.class); + } + + @Test + void getPayloadTypeWithCustomType() throws NoSuchMethodException { + Method m = TestClass.class.getDeclaredMethod("consumeWithCustomType", TestClass.MyType.class); + + Class result = typeToClassConverter.extractClass(extractFrom(m)); + + assertThat(result).isEqualTo(TestClass.MyType.class); + } + + @Test + void exceptionsAreTurnedIntoVoid() { + ParameterizedType spiedType = mock(ParameterizedType.class); + when(spiedType.getRawType()).thenThrow(new ClassCastException()); + + Class result = typeToClassConverter.extractClass(spiedType); + + assertThat(result).isEqualTo(Void.class); + } + + public static class TestClass { + + public void consumeWithString(String value) {} + + public void consumeWithGenericClass(Collection value) {} + + public void consumeWithMessageOfGenericClasses(Message> value) {} + + public void consumeWithMessageOfStringExtends(Message value) {} + + public void consumeWithMessageOfListOfString(Message> value) {} + + public void consumeWithMessageOfString(Message value) {} + + public void consumeWithCustomType(MyType value) {} + + public static class MyType extends GenericMessage { + public MyType(String payload) { + super(payload); + } + } + } + + private Type extractFrom(Method method) { + return method.getGenericParameterTypes()[0]; + } +} diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java index 8473846f3..3bbffdced 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java @@ -26,6 +26,8 @@ import io.github.springwolf.core.asyncapi.scanners.classes.ClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocket; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; @@ -81,7 +83,11 @@ public OperationAction getOperationType() { private final ComponentsService componentsService = new DefaultComponentsService(emptyList(), emptyList(), swaggerSchemaUtil, properties); private final AsyncApiDocketService asyncApiDocketService = mock(AsyncApiDocketService.class); - private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(properties); + private final TypeToClassConverter typeToClassConverter = new TypeToClassConverter(properties); + private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(typeToClassConverter); + + private final PayloadService payloadService = + new PayloadService(payloadClassExtractor, componentsService, properties); private final List operationBindingProcessors = List.of(new TestOperationBindingProcessor()); @@ -95,6 +101,7 @@ public OperationAction getOperationType() { classScanner, componentsService, payloadClassExtractor, + payloadService, operationBindingProcessors, messageBindingProcessors); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java index f656cc182..faf548d69 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java @@ -20,7 +20,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented; import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.jupiter.api.BeforeEach; @@ -40,7 +41,7 @@ class SpringAnnotationClassLevelOperationsScannerTest { - private final PayloadClassExtractor payloadClassExtractor = mock(PayloadClassExtractor.class); + private final PayloadService payloadService = mock(); private final BindingFactory bindingFactory = mock(BindingFactory.class); private final ComponentsService componentsService = mock(ComponentsService.class); SpringAnnotationClassLevelOperationsScanner scanner = @@ -49,7 +50,7 @@ class SpringAnnotationClassLevelOperationsScannerTest { TestMethodListener.class, bindingFactory, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); private static final String CHANNEL = "test-channel"; @@ -69,7 +70,8 @@ void setUp() { doReturn(defaultChannelBinding).when(bindingFactory).buildChannelBinding(any()); doReturn(defaultMessageBinding).when(bindingFactory).buildMessageBinding(any()); - doReturn(String.class).when(payloadClassExtractor).extractFrom(any()); + when(payloadService.extractSchema(any())) + .thenReturn(new NamedSchemaObject(String.class.getName(), new SchemaObject())); doAnswer(invocation -> invocation.>getArgument(0).getSimpleName()) .when(componentsService) .registerSchema(any(Class.class)); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java index 3e3bd0a58..64508e227 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java @@ -40,7 +40,8 @@ void asyncListenerAnnotationIsFound() { assertThat(asyncAPI.getChannels()).containsOnlyKeys("listener-channel"); assertThat(asyncAPI.getOperations()).containsOnlyKeys("listener-channel_receive_listen"); assertThat(asyncAPI.getComponents().getMessages()).containsOnlyKeys("java.lang.String"); - assertThat(asyncAPI.getComponents().getSchemas()).containsOnlyKeys("HeadersNotDocumented", "String"); + assertThat(asyncAPI.getComponents().getSchemas()) + .containsOnlyKeys("HeadersNotDocumented", "java.lang.String"); } } @@ -63,7 +64,8 @@ void asyncPublisherAnnotationIsFound() { assertThat(asyncAPI.getChannels()).containsOnlyKeys("publisher-channel"); assertThat(asyncAPI.getOperations()).containsOnlyKeys("publisher-channel_send_publish"); assertThat(asyncAPI.getComponents().getMessages()).containsOnlyKeys("java.lang.String"); - assertThat(asyncAPI.getComponents().getSchemas()).containsOnlyKeys("HeadersNotDocumented", "String"); + assertThat(asyncAPI.getComponents().getSchemas()) + .containsOnlyKeys("HeadersNotDocumented", "java.lang.String"); } } diff --git a/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/NoPayloadUsedConsumer.java b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/NoPayloadUsedConsumer.java new file mode 100644 index 000000000..9cdcd9a6e --- /dev/null +++ b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/NoPayloadUsedConsumer.java @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.examples.kafka.consumers; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class NoPayloadUsedConsumer { + + @KafkaListener(topics = "no-payload-used-topic") + public void receiveExamplePayload() { + log.info("Received new message in no-payload-used-topic"); + } +} diff --git a/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/XmlConsumer.java b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/XmlConsumer.java index b3a3d6ad2..ef091867c 100644 --- a/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/XmlConsumer.java +++ b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/XmlConsumer.java @@ -20,8 +20,10 @@ public class XmlConsumer { operation = @AsyncOperation( channelName = "xml-topic", - description = "Showcases a xml based message", - message = @AsyncMessage(contentType = "text/xml"))) + message = + @AsyncMessage( + contentType = "text/xml", + description = "Showcases a xml based message"))) @KafkaAsyncOperationBinding @KafkaListener(topics = "xml-topic") public void receiveExamplePayload(XmlPayloadDto payload) { diff --git a/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/YamlConsumer.java b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/YamlConsumer.java index dcfe24728..ea847da43 100644 --- a/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/YamlConsumer.java +++ b/springwolf-examples/springwolf-kafka-example/src/main/java/io/github/springwolf/examples/kafka/consumers/YamlConsumer.java @@ -20,8 +20,10 @@ public class YamlConsumer { operation = @AsyncOperation( channelName = "yaml-topic", - description = "Showcases a yaml based message", - message = @AsyncMessage(contentType = "application/yaml"))) + message = + @AsyncMessage( + contentType = "application/yaml", + description = "Showcases a yaml based message"))) @KafkaAsyncOperationBinding @KafkaListener(topics = "yaml-topic") public void receiveExamplePayload(YamlPayloadDto payload) { diff --git a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/SpringContextIntegrationTest.java b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/SpringContextIntegrationTest.java index bf09fc62f..e51884438 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/SpringContextIntegrationTest.java +++ b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/SpringContextIntegrationTest.java @@ -48,7 +48,7 @@ void testContextWithApplicationProperties() { @Test void testAllChannelsAreFound() { - assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(9); + assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(10); } } diff --git a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json index 7847279e5..f6d8acae6 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json @@ -66,6 +66,18 @@ } } }, + "no-payload-used-topic": { + "messages": { + "PayloadNotUsed": { + "$ref": "#/components/messages/PayloadNotUsed" + } + }, + "bindings": { + "kafka": { + "bindingVersion": "0.5.0" + } + } + }, "protobuf-topic": { "messages": { "io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message": { @@ -143,6 +155,19 @@ "type": "object" } }, + "PayloadNotUsed": { + "type": "object", + "properties": { }, + "description": "No payload specified", + "examples": [ + { } + ], + "x-json-schema": { + "$schema": "https://json-schema.org/draft-04/schema#", + "description": "No payload specified", + "type": "object" + } + }, "SpringDefaultHeaderAndCloudEvent": { "type": "object", "properties": { @@ -469,23 +494,23 @@ "type": "object" } }, - "SpringKafkaDefaultHeaders-String": { + "SpringKafkaDefaultHeaders-PayloadNotUsed": { "type": "object", "properties": { "__TypeId__": { "type": "string", "description": "Spring Type Id Header", "enum": [ - "java.lang.String" + "PayloadNotUsed" ], "examples": [ - "java.lang.String" + "PayloadNotUsed" ] } }, "examples": [ { - "__TypeId__": "java.lang.String" + "__TypeId__": "PayloadNotUsed" } ], "x-json-schema": { @@ -494,7 +519,7 @@ "__TypeId__": { "description": "Spring Type Id Header", "enum": [ - "java.lang.String" + "PayloadNotUsed" ], "type": "string" } @@ -502,23 +527,23 @@ "type": "object" } }, - "SpringKafkaDefaultHeaders-XmlPayloadDto": { + "SpringKafkaDefaultHeaders-String": { "type": "object", "properties": { "__TypeId__": { "type": "string", "description": "Spring Type Id Header", "enum": [ - "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" + "java.lang.String" ], "examples": [ - "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" + "java.lang.String" ] } }, "examples": [ { - "__TypeId__": "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" + "__TypeId__": "java.lang.String" } ], "x-json-schema": { @@ -527,7 +552,7 @@ "__TypeId__": { "description": "Spring Type Id Header", "enum": [ - "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" + "java.lang.String" ], "type": "string" } @@ -535,23 +560,23 @@ "type": "object" } }, - "SpringKafkaDefaultHeaders-YamlPayloadDto": { + "SpringKafkaDefaultHeaders-XmlPayloadDto": { "type": "object", "properties": { "__TypeId__": { "type": "string", "description": "Spring Type Id Header", "enum": [ - "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" ], "examples": [ - "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" ] } }, "examples": [ { - "__TypeId__": "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + "__TypeId__": "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" } ], "x-json-schema": { @@ -560,7 +585,7 @@ "__TypeId__": { "description": "Spring Type Id Header", "enum": [ - "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" ], "type": "string" } @@ -568,47 +593,33 @@ "type": "object" } }, - "String": { - "type": "string", - "examples": [ - "\"string\"" - ], - "x-json-schema": { - "$schema": "https://json-schema.org/draft-04/schema#", - "type": "string" - } - }, - "io.github.springwolf.addons.common_model_converters.converters.monetaryamount.MonetaryAmount": { + "SpringKafkaDefaultHeaders-YamlPayloadDto": { "type": "object", "properties": { - "amount": { - "type": "number", - "exclusiveMinimum": 0.01, - "examples": [ - 99.99 - ] - }, - "currency": { + "__TypeId__": { "type": "string", + "description": "Spring Type Id Header", + "enum": [ + "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + ], "examples": [ - "USD" + "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" ] } }, "examples": [ { - "amount": 99.99, - "currency": "USD" + "__TypeId__": "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" } ], "x-json-schema": { "$schema": "https://json-schema.org/draft-04/schema#", "properties": { - "amount": { - "exclusiveMinimum": 0.01, - "type": "number" - }, - "currency": { + "__TypeId__": { + "description": "Spring Type Id Header", + "enum": [ + "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + ], "type": "string" } }, @@ -1079,9 +1090,74 @@ }, "type": "string" } + }, + "java.lang.String": { + "type": "string", + "examples": [ + "\"string\"" + ], + "x-json-schema": { + "$schema": "https://json-schema.org/draft-04/schema#", + "type": "string" + } + }, + "javax.money.MonetaryAmount": { + "type": "object", + "properties": { + "amount": { + "type": "number", + "exclusiveMinimum": 0.01, + "examples": [ + 99.99 + ] + }, + "currency": { + "type": "string", + "examples": [ + "USD" + ] + } + }, + "examples": [ + { + "amount": 99.99, + "currency": "USD" + } + ], + "x-json-schema": { + "$schema": "https://json-schema.org/draft-04/schema#", + "properties": { + "amount": { + "exclusiveMinimum": 0.01, + "type": "number" + }, + "currency": { + "type": "string" + } + }, + "type": "object" + } } }, "messages": { + "PayloadNotUsed": { + "headers": { + "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-PayloadNotUsed" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/PayloadNotUsed" + } + }, + "name": "PayloadNotUsed", + "title": "PayloadNotUsed", + "bindings": { + "kafka": { + "bindingVersion": "0.5.0" + } + } + }, "io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope": { "headers": { "$ref": "#/components/schemas/HeadersNotUsed" @@ -1094,6 +1170,7 @@ }, "name": "io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope", "title": "StringEnvelope", + "description": "Payload description using @Schema annotation and @AsyncApiPayload within envelope class", "bindings": { "kafka": { "bindingVersion": "0.5.0" @@ -1211,6 +1288,7 @@ "contentType": "text/xml", "name": "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto", "title": "XmlPayloadDto", + "description": "Showcases a xml based message", "bindings": { "kafka": { "bindingVersion": "0.5.0" @@ -1230,6 +1308,7 @@ "contentType": "application/yaml", "name": "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto", "title": "YamlPayloadDto", + "description": "Showcases a yaml based message", "bindings": { "kafka": { "bindingVersion": "0.5.0" @@ -1243,7 +1322,7 @@ "payload": { "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", "schema": { - "$ref": "#/components/schemas/String" + "$ref": "#/components/schemas/java.lang.String" } }, "name": "java.lang.String", @@ -1261,7 +1340,7 @@ "payload": { "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", "schema": { - "$ref": "#/components/schemas/io.github.springwolf.addons.common_model_converters.converters.monetaryamount.MonetaryAmount" + "$ref": "#/components/schemas/javax.money.MonetaryAmount" } }, "name": "javax.money.MonetaryAmount", @@ -1390,6 +1469,22 @@ } ] }, + "no-payload-used-topic_receive_receiveExamplePayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/no-payload-used-topic" + }, + "bindings": { + "kafka": { + "bindingVersion": "0.5.0" + } + }, + "messages": [ + { + "$ref": "#/channels/no-payload-used-topic/messages/PayloadNotUsed" + } + ] + }, "protobuf-topic_receive_receiveExampleProtobufPayload": { "action": "receive", "channel": { @@ -1457,7 +1552,7 @@ "$ref": "#/channels/xml-topic" }, "title": "xml-topic_receive", - "description": "Showcases a xml based message", + "description": "Auto-generated description", "bindings": { "kafka": { "bindingVersion": "0.5.0" @@ -1475,7 +1570,7 @@ "$ref": "#/channels/yaml-topic" }, "title": "yaml-topic_receive", - "description": "Showcases a yaml based message", + "description": "Auto-generated description", "bindings": { "kafka": { "bindingVersion": "0.5.0" @@ -1488,4 +1583,4 @@ ] } } -} +} \ No newline at end of file diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/components/headers/AsyncHeadersForAmqpBuilder.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/components/headers/AsyncHeadersForAmqpBuilder.java index 11857854f..f7328b551 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/components/headers/AsyncHeadersForAmqpBuilder.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/components/headers/AsyncHeadersForAmqpBuilder.java @@ -3,6 +3,7 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; import java.util.Map; @@ -17,7 +18,7 @@ public class AsyncHeadersForAmqpBuilder implements AsyncHeadersBuilder { } @Override - public SchemaObject buildHeaders(Class payloadType) { + public SchemaObject buildHeaders(NamedSchemaObject payloadSchema) { return headers; } } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java index f9f000260..62c182c63 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java @@ -8,7 +8,7 @@ import io.github.springwolf.core.asyncapi.scanners.channels.annotations.SpringAnnotationClassLevelChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.channels.annotations.SpringAnnotationMethodLevelChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner; @@ -65,7 +65,7 @@ public SpringAnnotationChannelsScanner simpleRabbitClassLevelListenerAnnotationC SpringwolfClassScanner classScanner, AmqpBindingFactory amqpBindingBuilder, AsyncHeadersForAmqpBuilder asyncHeadersForAmqpBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationClassLevelChannelsScanner strategy = new SpringAnnotationClassLevelChannelsScanner<>( @@ -73,7 +73,7 @@ public SpringAnnotationChannelsScanner simpleRabbitClassLevelListenerAnnotationC RabbitHandler.class, amqpBindingBuilder, asyncHeadersForAmqpBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationChannelsScanner(classScanner, strategy); @@ -89,7 +89,7 @@ public SpringAnnotationOperationsScanner simpleRabbitClassLevelListenerAnnotatio SpringwolfClassScanner classScanner, AmqpBindingFactory amqpBindingBuilder, AsyncHeadersForAmqpBuilder asyncHeadersForAmqpBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationClassLevelOperationsScanner strategy = new SpringAnnotationClassLevelOperationsScanner<>( @@ -97,7 +97,7 @@ public SpringAnnotationOperationsScanner simpleRabbitClassLevelListenerAnnotatio RabbitHandler.class, amqpBindingBuilder, asyncHeadersForAmqpBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationOperationsScanner(classScanner, strategy); @@ -113,14 +113,14 @@ public SpringAnnotationChannelsScanner simpleRabbitMethodLevelListenerAnnotation SpringwolfClassScanner classScanner, AmqpBindingFactory amqpBindingBuilder, AsyncHeadersForAmqpBuilder asyncHeadersForAmqpBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelChannelsScanner strategy = new SpringAnnotationMethodLevelChannelsScanner<>( RabbitListener.class, amqpBindingBuilder, asyncHeadersForAmqpBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationChannelsScanner(classScanner, strategy); @@ -136,14 +136,14 @@ public SpringAnnotationOperationsScanner simpleRabbitMethodLevelListenerAnnotati SpringwolfClassScanner classScanner, AmqpBindingFactory amqpBindingBuilder, AsyncHeadersForAmqpBuilder asyncHeadersForAmqpBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelOperationsScanner strategy = new SpringAnnotationMethodLevelOperationsScanner<>( RabbitListener.class, amqpBindingBuilder, asyncHeadersForAmqpBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationOperationsScanner(classScanner, strategy); diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java index 586e9fde2..521634da4 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java @@ -5,6 +5,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.amqp.controller.SpringwolfAmqpController; @@ -49,7 +51,9 @@ public class SpringwolfAmqpProducerConfigurationIntegrationTest { @MockBean(RabbitTemplate.class), @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(AsyncApiDocketService.class) }) @Nested @@ -90,7 +94,9 @@ void springwolfAmqpProducerShouldBePresentInSpringContext() { @MockBean(RabbitTemplate.class), @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), }) @Nested class AmqpProducerWillNotBeCreatedIfDisabledTest { diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScanner.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScanner.java index 75637ee6f..9b85c997a 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScanner.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScanner.java @@ -82,7 +82,8 @@ private Map.Entry toChannelEntry(FunctionalChannelBeanDat private ChannelObject buildChannel(FunctionalChannelBeanData beanData) { Class payloadType = beanData.payloadType(); - String modelName = componentsService.registerSchema(payloadType); + String modelName = componentsService.registerSchema( + payloadType); // TODO: switch to payloadService? (same for operatoinScanner) String headerModelName = componentsService.registerSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED); var messagePayload = MessagePayload.of(MultiFormatSchema.builder() @@ -92,7 +93,7 @@ private ChannelObject buildChannel(FunctionalChannelBeanData beanData) { Map messageBinding = buildMessageBinding(beanData.annotatedElement()); MessageObject message = MessageObject.builder() .name(payloadType.getName()) - .title(modelName) + .title(payloadType.getSimpleName()) .payload(messagePayload) .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) .bindings(messageBinding) diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java index c8dfd9427..a4edbed4c 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilder.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.plugins.cloudstream.asyncapi.scanners.common; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import lombok.RequiredArgsConstructor; import java.lang.reflect.AnnotatedElement; @@ -20,7 +20,7 @@ @RequiredArgsConstructor public class FunctionalChannelBeanBuilder { - private final PayloadClassExtractor extractor; + private final TypeToClassConverter typeToClassConverter; public Set build(AnnotatedElement element) { Class type = getRawType(element); @@ -119,7 +119,7 @@ private List> getTypeGenerics(Class c) { private List> getTypeGenerics(ParameterizedType parameterizedType) { return Arrays.stream(parameterizedType.getActualTypeArguments()) - .map(extractor::typeToClass) + .map(typeToClassConverter::extractClass) .collect(toList()); } } diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/configuration/SpringwolfCloudStreamAutoConfiguration.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/configuration/SpringwolfCloudStreamAutoConfiguration.java index b10d537f1..19bc76cdb 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/configuration/SpringwolfCloudStreamAutoConfiguration.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/springwolf/plugins/cloudstream/configuration/SpringwolfCloudStreamAutoConfiguration.java @@ -6,7 +6,7 @@ import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ChannelBindingProcessor; import io.github.springwolf.core.asyncapi.scanners.bindings.messages.MessageBindingProcessor; import io.github.springwolf.core.asyncapi.scanners.classes.spring.ComponentClassScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants; import io.github.springwolf.plugins.cloudstream.asyncapi.scanners.channels.CloudStreamFunctionChannelsScanner; @@ -65,7 +65,7 @@ public CloudStreamFunctionOperationsScanner cloudStreamFunctionOperationsScanner } @Bean - public FunctionalChannelBeanBuilder functionalChannelBeanBuilder(PayloadClassExtractor payloadClassExtractor) { - return new FunctionalChannelBeanBuilder(payloadClassExtractor); + public FunctionalChannelBeanBuilder functionalChannelBeanBuilder(TypeToClassConverter typeToClassConverter) { + return new FunctionalChannelBeanBuilder(typeToClassConverter); } } diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java index 3e3e8c78b..d13095d40 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java @@ -26,7 +26,7 @@ import io.github.springwolf.core.asyncapi.scanners.beans.DefaultBeanMethodsScanner; import io.github.springwolf.core.asyncapi.scanners.classes.spring.ComponentClassScanner; import io.github.springwolf.core.asyncapi.scanners.classes.spring.ConfigurationClassScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.DefaultAsyncApiDocketService; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import io.github.springwolf.plugins.cloudstream.asyncapi.scanners.common.FunctionalChannelBeanBuilder; @@ -64,7 +64,7 @@ DefaultBeanMethodsScanner.class, DefaultComponentsService.class, SwaggerSchemaUtil.class, - PayloadClassExtractor.class, + TypeToClassConverter.class, DefaultSchemaWalker.class, SchemaWalkerProvider.class, ExampleJsonValueGenerator.class, @@ -127,7 +127,7 @@ void testConsumerBinding() { .name(String.class.getName()) .title(String.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .schema(SchemaReference.fromSchema(String.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) @@ -171,7 +171,7 @@ void testSupplierBinding() { .name(String.class.getName()) .title(String.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .schema(SchemaReference.fromSchema(String.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) @@ -248,7 +248,7 @@ void testFunctionBinding() { .name(String.class.getName()) .title(String.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .schema(SchemaReference.fromSchema(String.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) @@ -302,7 +302,7 @@ void testKStreamFunctionBinding() { .name(Integer.class.getName()) .title(Integer.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(Integer.class.getSimpleName())) + .schema(SchemaReference.fromSchema(Integer.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) @@ -327,7 +327,7 @@ void testKStreamFunctionBinding() { .name(String.class.getName()) .title(String.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .schema(SchemaReference.fromSchema(String.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) @@ -382,7 +382,7 @@ void testFunctionBindingWithSameTopicName() { .name(Integer.class.getName()) .title(Integer.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(Integer.class.getSimpleName())) + .schema(SchemaReference.fromSchema(Integer.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) @@ -402,7 +402,7 @@ void testFunctionBindingWithSameTopicName() { .name(String.class.getName()) .title(String.class.getSimpleName()) .payload(MessagePayload.of(MultiFormatSchema.builder() - .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .schema(SchemaReference.fromSchema(String.class.getName())) .build())) .headers(MessageHeaders.of( MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle()))) diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java index 4260cfdad..7791ce021 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/common/FunctionalChannelBeanBuilderTest.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.plugins.cloudstream.asyncapi.scanners.common; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import org.apache.kafka.streams.kstream.KStream; import org.assertj.core.api.Assertions; @@ -23,7 +23,7 @@ class FunctionalChannelBeanBuilderTest { private final SpringwolfConfigProperties properties = new SpringwolfConfigProperties(); private final FunctionalChannelBeanBuilder functionalChannelBeanBuilder = - new FunctionalChannelBeanBuilder(new PayloadClassExtractor(properties)); + new FunctionalChannelBeanBuilder(new TypeToClassConverter(properties)); @Nested class FromMethod { diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsScannerConfiguration.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsScannerConfiguration.java index 93a3375cf..400ffdb7f 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsScannerConfiguration.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsScannerConfiguration.java @@ -8,7 +8,7 @@ import io.github.springwolf.core.asyncapi.scanners.channels.SpringAnnotationChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.channels.annotations.SpringAnnotationMethodLevelChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner; import io.github.springwolf.plugins.jms.asyncapi.scanners.bindings.JmsBindingFactory; @@ -41,14 +41,14 @@ public JmsBindingFactory jmsBindingBuilder() { public SpringAnnotationChannelsScanner simpleJmsMethodLevelListenerAnnotationChannelsScanner( SpringwolfClassScanner classScanner, JmsBindingFactory jmsBindingBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelChannelsScanner strategy = new SpringAnnotationMethodLevelChannelsScanner<>( JmsListener.class, jmsBindingBuilder, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationChannelsScanner(classScanner, strategy); @@ -60,14 +60,14 @@ public SpringAnnotationChannelsScanner simpleJmsMethodLevelListenerAnnotationCha public SpringAnnotationOperationsScanner simpleJmsMethodLevelListenerAnnotationOperationsScanner( SpringwolfClassScanner classScanner, JmsBindingFactory jmsBindingBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelOperationsScanner strategy = new SpringAnnotationMethodLevelOperationsScanner<>( JmsListener.class, jmsBindingBuilder, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationOperationsScanner(classScanner, strategy); diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java index 4c7a80245..45ee11a55 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java @@ -6,6 +6,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.jms.controller.SpringwolfJmsController; @@ -50,7 +52,9 @@ public class SpringwolfJmsProducerConfigurationIntegrationTest { @MockBean(ComponentsService.class), @MockBean(AsyncApiDocketService.class), @MockBean(AsyncApiService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(JmsTemplate.class) }) @Nested @@ -90,7 +94,9 @@ void springwolfJmsProducerShouldBePresentInSpringContext() { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), @MockBean(ChannelsService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(JmsTemplate.class) }) @Nested diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsControllerIntegrationTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsControllerIntegrationTest.java index 3dc624517..9c6363aa3 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsControllerIntegrationTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsControllerIntegrationTest.java @@ -9,6 +9,7 @@ import io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker; import io.github.springwolf.core.asyncapi.components.examples.walkers.json.ExampleJsonValueGenerator; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.jms.producer.SpringwolfJmsProducer; @@ -47,6 +48,7 @@ PublishingPayloadCreator.class, SpringwolfJmsProducer.class, PayloadClassExtractor.class, + TypeToClassConverter.class, DefaultComponentsService.class, SwaggerSchemaUtil.class, DefaultSchemaWalker.class, @@ -62,7 +64,6 @@ "springwolf.docket.servers.jms.protocol=jms", "springwolf.docket.servers.jms.host=127.0.0.1", "springwolf.plugin.jms.publishing.enabled=true", - "springwolf.use-fqn=true" }) class SpringwolfJmsControllerIntegrationTest { diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/components/header/AsyncHeadersForKafkaBuilder.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/components/header/AsyncHeadersForKafkaBuilder.java index ea7b6bb24..247e8bf1c 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/components/header/AsyncHeadersForKafkaBuilder.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/asyncapi/components/header/AsyncHeadersForKafkaBuilder.java @@ -3,12 +3,14 @@ import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder; +import io.github.springwolf.core.asyncapi.scanners.common.payload.NamedSchemaObject; public class AsyncHeadersForKafkaBuilder implements AsyncHeadersBuilder { @Override - public SchemaObject buildHeaders(Class payloadType) { - return new AsyncHeadersForSpringKafkaBuilder("SpringKafkaDefaultHeaders-" + payloadType.getSimpleName()) - .withTypeIdHeader(payloadType.getTypeName()) + public SchemaObject buildHeaders(NamedSchemaObject payloadSchema) { + return new AsyncHeadersForSpringKafkaBuilder( + "SpringKafkaDefaultHeaders-" + payloadSchema.schema().getTitle()) + .withTypeIdHeader(payloadSchema.name()) .build(); } } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java index bb4c1f797..b8972404e 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaScannerConfiguration.java @@ -8,7 +8,7 @@ import io.github.springwolf.core.asyncapi.scanners.channels.annotations.SpringAnnotationClassLevelChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.channels.annotations.SpringAnnotationMethodLevelChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner; @@ -60,7 +60,7 @@ public SpringAnnotationChannelsScanner simpleKafkaClassLevelListenerAnnotationCh SpringwolfClassScanner classScanner, KafkaBindingFactory kafkaBindingBuilder, AsyncHeadersForKafkaBuilder asyncHeadersForKafkaBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationClassLevelChannelsScanner strategy = new SpringAnnotationClassLevelChannelsScanner<>( @@ -68,7 +68,7 @@ public SpringAnnotationChannelsScanner simpleKafkaClassLevelListenerAnnotationCh KafkaHandler.class, kafkaBindingBuilder, asyncHeadersForKafkaBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationChannelsScanner(classScanner, strategy); @@ -84,7 +84,7 @@ public SpringAnnotationOperationsScanner simpleKafkaClassLevelListenerAnnotation SpringwolfClassScanner classScanner, KafkaBindingFactory kafkaBindingBuilder, AsyncHeadersForKafkaBuilder asyncHeadersForKafkaBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationClassLevelOperationsScanner strategy = new SpringAnnotationClassLevelOperationsScanner<>( @@ -92,7 +92,7 @@ public SpringAnnotationOperationsScanner simpleKafkaClassLevelListenerAnnotation KafkaHandler.class, kafkaBindingBuilder, asyncHeadersForKafkaBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationOperationsScanner(classScanner, strategy); @@ -108,14 +108,14 @@ public SpringAnnotationChannelsScanner simpleKafkaMethodLevelListenerAnnotationC SpringwolfClassScanner classScanner, KafkaBindingFactory kafkaBindingBuilder, AsyncHeadersForKafkaBuilder asyncHeadersForKafkaBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelChannelsScanner strategy = new SpringAnnotationMethodLevelChannelsScanner<>( KafkaListener.class, kafkaBindingBuilder, asyncHeadersForKafkaBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationChannelsScanner(classScanner, strategy); @@ -131,14 +131,14 @@ public SpringAnnotationOperationsScanner simpleKafkaMethodLevelListenerAnnotatio SpringwolfClassScanner classScanner, KafkaBindingFactory kafkaBindingBuilder, AsyncHeadersForKafkaBuilder asyncHeadersForKafkaBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelOperationsScanner strategy = new SpringAnnotationMethodLevelOperationsScanner<>( KafkaListener.class, kafkaBindingBuilder, asyncHeadersForKafkaBuilder, - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationOperationsScanner(classScanner, strategy); diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java index 08eeb6542..8cdc74772 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java @@ -4,6 +4,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.kafka.controller.SpringwolfKafkaController; @@ -46,7 +48,9 @@ public class SpringwolfKafkaProducerConfigurationIntegrationTest { value = { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(AsyncApiDocketService.class) }) class KafkaProducerWillBeCreatedIfEnabledTest { @@ -85,7 +89,9 @@ void springwolfKafkaTemplateShouldBePresentInSpringContext() { value = { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), - @MockBean(PayloadClassExtractor.class) + @MockBean(PayloadService.class), + @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), }) class KafkaProducerWillNotBeCreatedIfDisabledTest { @Autowired diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaControllerIntegrationTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaControllerIntegrationTest.java index b2d09c986..bb8c91fd3 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaControllerIntegrationTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaControllerIntegrationTest.java @@ -9,6 +9,7 @@ import io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker; import io.github.springwolf.core.asyncapi.components.examples.walkers.json.ExampleJsonValueGenerator; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.kafka.producer.SpringwolfKafkaProducer; @@ -49,6 +50,7 @@ DefaultComponentsService.class, SwaggerSchemaUtil.class, PayloadClassExtractor.class, + TypeToClassConverter.class, DefaultSchemaWalker.class, SchemaWalkerProvider.class, ExampleJsonValueGenerator.class, @@ -62,7 +64,6 @@ "springwolf.docket.servers.kafka.protocol=kafka", "springwolf.docket.servers.kafka.host=127.0.0.1", "springwolf.plugin.kafka.publishing.enabled=true", - "springwolf.use-fqn=true" }) class SpringwolfKafkaControllerIntegrationTest { diff --git a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/springwolf/plugins/sns/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/springwolf/plugins/sns/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java index e177e174d..1ef709974 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/springwolf/plugins/sns/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/springwolf/plugins/sns/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java @@ -7,6 +7,7 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.sns.controller.SpringwolfSnsController; @@ -49,6 +50,7 @@ public class SpringwolfSnsProducerConfigurationIntegrationTest { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(AsyncApiDocketService.class), @MockBean(AsyncApiService.class), @MockBean(SnsTemplate.class) @@ -90,6 +92,7 @@ void springwolfSqsProducerShouldBePresentInSpringContext() { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(ChannelsService.class), @MockBean(SnsTemplate.class) }) diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsScannerConfiguration.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsScannerConfiguration.java index a40f0279e..50ea0ca3c 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsScannerConfiguration.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsScannerConfiguration.java @@ -9,7 +9,7 @@ import io.github.springwolf.core.asyncapi.scanners.channels.SpringAnnotationChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.channels.annotations.SpringAnnotationMethodLevelChannelsScanner; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; -import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; import io.github.springwolf.core.asyncapi.scanners.operations.SpringAnnotationOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner; import io.github.springwolf.plugins.sqs.asyncapi.scanners.bindings.SqsBindingFactory; @@ -41,14 +41,14 @@ public SqsBindingFactory sqsBindingBuilder() { public SpringAnnotationChannelsScanner simpleSqsMethodLevelListenerAnnotationChannelsScanner( SpringwolfClassScanner classScanner, SqsBindingFactory sqsBindingBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelChannelsScanner strategy = new SpringAnnotationMethodLevelChannelsScanner<>( SqsListener.class, sqsBindingBuilder, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationChannelsScanner(classScanner, strategy); @@ -60,14 +60,14 @@ public SpringAnnotationChannelsScanner simpleSqsMethodLevelListenerAnnotationCha public SpringAnnotationOperationsScanner simpleSqsMethodLevelListenerAnnotationOperationsScanner( SpringwolfClassScanner classScanner, SqsBindingFactory sqsBindingBuilder, - PayloadClassExtractor payloadClassExtractor, + PayloadService payloadService, ComponentsService componentsService) { SpringAnnotationMethodLevelOperationsScanner strategy = new SpringAnnotationMethodLevelOperationsScanner<>( SqsListener.class, sqsBindingBuilder, new AsyncHeadersNotDocumented(), - payloadClassExtractor, + payloadService, componentsService); return new SpringAnnotationOperationsScanner(classScanner, strategy); diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java index 04ab4c6c2..61ac56860 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/springwolf/plugins/sqs/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java @@ -7,6 +7,8 @@ import io.github.springwolf.core.asyncapi.components.ComponentsService; import io.github.springwolf.core.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor; +import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadService; +import io.github.springwolf.core.asyncapi.scanners.common.payload.TypeToClassConverter; import io.github.springwolf.core.configuration.docket.AsyncApiDocketService; import io.github.springwolf.core.controller.PublishingPayloadCreator; import io.github.springwolf.plugins.sqs.controller.SpringwolfSqsController; @@ -48,7 +50,9 @@ public class SpringwolfSqsProducerConfigurationIntegrationTest { value = { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(AsyncApiDocketService.class), @MockBean(AsyncApiService.class), @MockBean(SqsTemplate.class) @@ -89,7 +93,9 @@ void springwolfSqsProducerShouldBePresentInSpringContext() { value = { @MockBean(SpringwolfClassScanner.class), @MockBean(ComponentsService.class), + @MockBean(PayloadService.class), @MockBean(PayloadClassExtractor.class), + @MockBean(TypeToClassConverter.class), @MockBean(ChannelsService.class), @MockBean(SqsTemplate.class) })