Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC for Polymorphism #890

Merged
merged 22 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.Message;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import jakarta.annotation.Nullable;

import java.lang.reflect.Type;
import java.util.Map;

public interface ComponentsService {

Map<String, SchemaObject> getSchemas();

@Nullable
SchemaObject resolveSchema(String schemaName);
ComponentSchema resolvePayloadSchema(Type type, String contentType);

String registerSchema(SchemaObject headers);

String resolvePayloadSchema(Class<?> type, String contentType);

Map<String, Message> getMessages();

MessageReference registerMessage(MessageObject message);

String getSchemaName(Type type);

String getSimpleSchemaName(Type type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.Message;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -27,11 +29,10 @@ public Map<String, SchemaObject> getSchemas() {
}

@Override
public SchemaObject resolveSchema(String schemaName) {
if (schemas.containsKey(schemaName)) {
return schemas.get(schemaName);
}
return null;
public ComponentSchema resolvePayloadSchema(Type type, String contentType) {
SwaggerSchemaService.Payload payload = schemaService.resolvePayloadSchema(type, contentType);
payload.referencedSchemas().forEach(this.schemas::putIfAbsent);
return payload.payloadSchema();
}

@Override
Expand All @@ -44,16 +45,6 @@ public String registerSchema(SchemaObject headers) {
return headers.getTitle();
}

@Override
public String resolvePayloadSchema(Class<?> type, String contentType) {
log.debug("Registering schema for {}", type.getSimpleName());

SwaggerSchemaService.ExtractedSchemas schemas = schemaService.extractSchema(type, contentType);
schemas.schemas().forEach(this.schemas::putIfAbsent);

return schemas.rootSchemaName();
}

@Override
public Map<String, Message> getMessages() {
return this.messages;
Expand All @@ -67,4 +58,14 @@ public MessageReference registerMessage(MessageObject message) {

return MessageReference.toComponentMessage(message);
}

@Override
public String getSchemaName(Type type) {
return schemaService.getNameFromType(type);
}

@Override
public String getSimpleSchemaName(Type type) {
return schemaService.getSimpleNameFromType(type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
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;
Expand Down Expand Up @@ -99,13 +98,12 @@ protected MessageObject buildMessage(AsyncOperation operationData, Method method
Map<String, MessageBinding> messageBinding =
AsyncAnnotationUtil.processMessageBindingFromAnnotation(method, messageBindingProcessors);

var messagePayload = MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(payloadSchema.name()))
.build());
var messagePayload = MessagePayload.of(
MultiFormatSchema.builder().schema(payloadSchema.payload()).build());

String description = operationData.message().description();
if (StringUtils.isBlank(description) && payloadSchema.schema() != null) {
description = payloadSchema.schema().getDescription();
if (StringUtils.isBlank(description) && payloadSchema.payload() instanceof SchemaObject) {
description = ((SchemaObject) payloadSchema.payload()).getDescription();
}
if (StringUtils.isNotBlank(description)) {
description = this.resolver.resolveStringValue(description);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersBuilder;
Expand Down Expand Up @@ -102,9 +101,8 @@ protected MessageObject buildMessage(

Map<String, MessageBinding> messageBinding = bindingFactory.buildMessageBinding(classAnnotation, headerSchema);

MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(payloadSchema.name()))
.build());
MessagePayload payload = MessagePayload.of(
MultiFormatSchema.builder().schema(payloadSchema.payload()).build());

MessageObject message = MessageObject.builder()
.messageId(payloadSchema.name())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersBuilder;
Expand Down Expand Up @@ -36,9 +35,8 @@ protected MessageObject buildMessage(

Map<String, MessageBinding> messageBinding = bindingFactory.buildMessageBinding(annotation, mergedHeaderSchema);

MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(payloadSchema.name()))
.build());
MessagePayload payload = MessagePayload.of(
MultiFormatSchema.builder().schema(payloadSchema.payload()).build());

MessageObject message = MessageObject.builder()
.messageId(payloadSchema.name())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.RequiredArgsConstructor;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Optional;

@RequiredArgsConstructor
Expand All @@ -15,7 +16,7 @@ public class PayloadAsyncOperationService {
private final PayloadService payloadService;

public PayloadSchemaObject extractSchema(AsyncOperation operationData, Method method) {
Optional<Class<?>> payloadType = operationData.payloadType() != Object.class
Optional<Type> payloadType = operationData.payloadType() != Object.class
? Optional.of(operationData.payloadType())
: payloadClassExtractor.extractFrom(method);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.RequiredArgsConstructor;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Optional;

@RequiredArgsConstructor
Expand All @@ -14,7 +15,7 @@ public class PayloadMethodParameterService implements PayloadMethodService {
private final PayloadService payloadService;

public PayloadSchemaObject extractSchema(Method method) {
Optional<Class<?>> payloadType = payloadClassExtractor.extractFrom(method);
Optional<Type> payloadType = payloadClassExtractor.extractFrom(method);

return payloadType.map(payloadService::buildSchema).orElseGet(payloadService::useUnusedPayload);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
// 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.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import jakarta.annotation.Nullable;

/**
* Encapsulates the resolved name for the contained schema.
*
* @param name The fully qualified name or the simple name of the schema.
* @param simpleSchemaName
* @param schema The SchemaObject.
* @param schemaPayload The schema-payload to be inserted in the message, when not null this schema will override the payload of the message.
*/
public record PayloadSchemaObject(String name, @Nullable SchemaObject schema) {
public record PayloadSchemaObject(String name, String simpleSchemaName, @Nullable ComponentSchema schema) {
public String title() {
return schema != null ? schema.getTitle() : name();
return (simpleSchemaName() != null) ? simpleSchemaName() : name();
}

public Object payload() {
if (schema() != null) {
if (schema().getSchema() != null) {
return schema().getSchema();
}
if (schema().getReference() != null) {
return schema().getReference();
}
if (schema().getMultiFormatSchema() != null) {
return schema().getMultiFormatSchema();
}
}
return MessageReference.toSchema(name());
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.common.payload.internal;

import lombok.RequiredArgsConstructor;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties;
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.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;

@RequiredArgsConstructor
@Slf4j
public class PayloadClassExtractor {
private final TypeToClassConverter typeToClassConverter;
private final Map<String, Integer> extractableClassToArgumentIndex;

public Optional<Class<?>> extractFrom(Method method) {
public PayloadClassExtractor(SpringwolfConfigProperties properties) {
if (properties.getPayload() != null) {
extractableClassToArgumentIndex = properties.getPayload().getExtractableClasses();
} else {
extractableClassToArgumentIndex = Map.of();
}
}

public Optional<Type> extractFrom(Method method) {
String methodName = String.format("%s::%s", method.getDeclaringClass().getSimpleName(), method.getName());
log.debug("Finding payload type for {}", methodName);

return getPayloadParameterIndex(method.getParameterTypes(), method.getParameterAnnotations(), methodName)
.map((parameterPayloadIndex) ->
typeToClassConverter.extractClass(method.getGenericParameterTypes()[parameterPayloadIndex]));
.map((parameterPayloadIndex) -> method.getGenericParameterTypes()[parameterPayloadIndex])
.map(this::extractActualType);
}

private Optional<Integer> getPayloadParameterIndex(
Expand Down Expand Up @@ -56,4 +67,32 @@ private int getPayloadAnnotatedParameterIndex(Annotation[][] parameterAnnotation

return -1;
}

private Type extractActualType(Type parameterType) {
// TODO: add tests / adapt from TypeToClassConverterTest
Type type = parameterType;

while (type instanceof ParameterizedType typeParameterized) {
String typeName = ((ParameterizedType) type).getRawType().getTypeName();
if (!extractableClassToArgumentIndex.containsKey(typeName)) {
break;
}

Integer index = extractableClassToArgumentIndex.get(typeName);
type = typeParameterized.getActualTypeArguments()[index];

if (type instanceof WildcardType) {
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
Type[] lowerBounds = ((WildcardType) type).getLowerBounds();
if (upperBounds.length > 0 && upperBounds[0] != Object.class) {
type = upperBounds[0];
}
if (lowerBounds.length > 0 && lowerBounds[0] != Object.class) {
type = lowerBounds[0];
}
}
}

return type;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.common.payload.internal;

import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
Expand All @@ -9,6 +10,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Type;
import java.util.Map;

@Slf4j
Expand All @@ -20,34 +22,32 @@ public class PayloadService {
private static final String PAYLOAD_NOT_USED_KEY = "PayloadNotUsed";
public static final PayloadSchemaObject PAYLOAD_NOT_USED = new PayloadSchemaObject(
PAYLOAD_NOT_USED_KEY,
SchemaObject.builder()
PAYLOAD_NOT_USED_KEY,
ComponentSchema.of(SchemaObject.builder()
.type(SchemaType.OBJECT)
.title(PAYLOAD_NOT_USED_KEY)
.description("No payload specified")
.properties(Map.of())
.build());
.build()));

public PayloadSchemaObject buildSchema(Class<?> payloadType) {
public PayloadSchemaObject buildSchema(Type payloadType) {
String contentType = properties.getDocket().getDefaultContentType();

return buildSchema(contentType, payloadType);
}

public PayloadSchemaObject buildSchema(String contentType, Class<?> payloadType) {
String componentsSchemaName = this.componentsService.resolvePayloadSchema(payloadType, contentType);

SchemaObject schema = componentsService.resolveSchema(componentsSchemaName);
if (schema != null) {
schema.setTitle(payloadType.getSimpleName());
dabeck81 marked this conversation as resolved.
Show resolved Hide resolved
}
public PayloadSchemaObject buildSchema(String contentType, Type payloadType) {
String schemaName = componentsService.getSchemaName(payloadType);
String simpleSchemaName = componentsService.getSimpleSchemaName(payloadType);

return new PayloadSchemaObject(componentsSchemaName, schema);
ComponentSchema schema = componentsService.resolvePayloadSchema(payloadType, contentType);
return new PayloadSchemaObject(schemaName, simpleSchemaName, schema);
}

public PayloadSchemaObject useUnusedPayload() {
SchemaObject schema = PAYLOAD_NOT_USED.schema();
if (schema != null) {
this.componentsService.registerSchema(schema);
ComponentSchema schema = PAYLOAD_NOT_USED.schema();
if (schema != null && schema.getSchema() != null) {
this.componentsService.registerSchema(schema.getSchema());
}
return PAYLOAD_NOT_USED;
}
Expand Down
Loading
Loading