Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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,6 +4,7 @@
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -17,6 +18,7 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@AsyncOperationBinding
@Inherited
public @interface AsyncGenericOperationBinding {
/**
* The name of the binding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.github.stavshamir.springwolf.asyncapi.types.OperationData;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand Down Expand Up @@ -33,6 +34,7 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Repeatable(AsyncListeners.class)
@Inherited
public @interface AsyncListener {
/**
* Mapped to {@link OperationData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotationCollectors;
import org.springframework.core.annotation.MergedAnnotationPredicates;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.util.StringValueResolver;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -66,8 +73,9 @@ private Stream<Method> getAnnotatedMethods(Class<?> type) {
log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName());

return Arrays.stream(type.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(annotationClass)
|| method.isAnnotationPresent(annotationClassRepeatable));
.filter(method -> !method.isBridge())
.filter(method -> AnnotationUtils.findAnnotation(method, annotationClass) != null
|| AnnotationUtils.findAnnotation(method, annotationClassRepeatable) != null);
}

private Stream<OperationData> toOperationData(Method method) {
Expand All @@ -80,7 +88,16 @@ private Stream<OperationData> toOperationData(Method method) {
Message message = AsyncAnnotationScannerUtil.processMessageFromAnnotation(method);

Class<AsyncListener> annotationClass = AsyncListener.class;
return Arrays.stream(method.getAnnotationsByType(annotationClass))
Set<AsyncListener> annotations = MergedAnnotations.from(
method,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY,
RepeatableContainers.standardRepeatables())
.stream(annotationClass)
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toAnnotationSet());

return annotations.stream()
.map(annotation -> toConsumerData(method, operationBindings, messageBindings, message, annotation));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@interface AsyncListeners {
@Inherited
public @interface AsyncListeners {
AsyncListener[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -15,6 +16,7 @@
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@Inherited
public @interface AsyncMessage {
/**
* Mapped to {@link Message#getDescription()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaderSchema;
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation is mapped to {@link OperationData}
*/
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@Inherited
public @interface AsyncOperation {
/**
* Mapped to {@link OperationData#getChannelName()}
Expand Down Expand Up @@ -50,7 +52,8 @@

@Retention(RetentionPolicy.CLASS)
@Target({})
@interface Headers {
@Inherited
public @interface Headers {
/**
* Mapped to {@link AsyncHeaders#getSchemaName()}
*/
Expand All @@ -60,7 +63,8 @@

@Retention(RetentionPolicy.CLASS)
@Target({})
@interface Header {
@Inherited
public @interface Header {
/**
* Mapped to {@link AsyncHeaderSchema#getHeaderName()}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.github.stavshamir.springwolf.asyncapi.types.OperationData;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand Down Expand Up @@ -32,6 +33,7 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Repeatable(AsyncPublishers.class)
@Inherited
public @interface AsyncPublisher {
/**
* Mapped to {@link OperationData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotationCollectors;
import org.springframework.core.annotation.MergedAnnotationPredicates;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.util.StringValueResolver;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

@Slf4j
Expand Down Expand Up @@ -66,8 +73,9 @@ private Stream<Method> getAnnotatedMethods(Class<?> type) {
log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName());

return Arrays.stream(type.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(annotationClass)
|| method.isAnnotationPresent(annotationClassRepeatable));
.filter(method -> !method.isBridge())
.filter(method -> AnnotationUtils.findAnnotation(method, annotationClass) != null
|| AnnotationUtils.findAnnotation(method, annotationClassRepeatable) != null);
}

private Stream<OperationData> toOperationData(Method method) {
Expand All @@ -80,7 +88,16 @@ private Stream<OperationData> toOperationData(Method method) {
Message message = AsyncAnnotationScannerUtil.processMessageFromAnnotation(method);

Class<AsyncPublisher> annotationClass = AsyncPublisher.class;
return Arrays.stream(method.getAnnotationsByType(annotationClass))
Set<AsyncPublisher> annotations = MergedAnnotations.from(
method,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY,
RepeatableContainers.standardRepeatables())
.stream(annotationClass)
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toAnnotationSet());

return annotations.stream()
.map(annotation -> toConsumerData(method, operationBindings, messageBindings, message, annotation));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@interface AsyncPublishers {
@Inherited
public @interface AsyncPublishers {
AsyncPublisher[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.core.annotation.Order;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -27,6 +28,7 @@ protected ProcessedOperationBinding mapToOperationBinding(TestOperationBinding b
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@AsyncOperationBinding
@Inherited
public @interface TestOperationBinding {
TestMessageBindingProcessor.TestMessageBinding operationBinding() default
@TestMessageBindingProcessor.TestMessageBinding();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.core.annotation.Order;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand Down Expand Up @@ -37,5 +38,6 @@ private ProcessedMessageBinding mapToMessageBinding(

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Inherited
public @interface TestMessageBinding {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.core.annotation.Order;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -30,6 +31,7 @@ public Optional<ProcessedOperationBinding> process(Method method) {

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Inherited
public @interface TestOperationBinding {
TestMessageBindingProcessor.TestMessageBinding operationBinding() default
@TestMessageBindingProcessor.TestMessageBinding();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand Down Expand Up @@ -119,5 +120,6 @@ private static class SimpleFoo {

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestChannelListener {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ContextConfiguration;
Expand Down Expand Up @@ -54,7 +56,7 @@
"test.property.server1=server1",
"test.property.server2=server2"
})
class AsyncListenerAnnotationScannerTest {
class AsyncListenerAnnotationScannerIntegrationTest {

@Autowired
private AsyncListenerAnnotationScanner channelScanner;
Expand Down Expand Up @@ -243,6 +245,39 @@ void scan_componentHasAsyncMethodAnnotation() {
assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel));
}

@ParameterizedTest
@ValueSource(classes = {ClassImplementingInterfaceWithAnnotation.class})
void scan_componentHasOnlyDeclaredMethods(Class<?> clazz) {
// Given a class with a method, which is declared in a generic interface
setClassToScan(clazz);

// When scan is called
Map<String, ChannelItem> actualChannels = channelScanner.scan();

// Then the returned collection contains the channel with the actual method, excluding type erased methods
Message message = Message.builder()
.name(String.class.getName())
.title(String.class.getSimpleName())
.description(null)
.payload(PayloadReference.fromModelName(String.class.getSimpleName()))
.schemaFormat("application/vnd.oai.openapi+json;version=3.0.0")
.headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))
.bindings(EMPTY_MAP)
.build();

Operation operation = Operation.builder()
.description("test channel operation description")
.operationId("test-channel_publish")
.bindings(EMPTY_MAP)
.message(message)
.build();

ChannelItem expectedChannel =
ChannelItem.builder().bindings(null).publish(operation).build();

assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel));
}

private static class ClassWithoutListenerAnnotation {

private void methodWithoutAnnotation() {}
Expand Down Expand Up @@ -319,6 +354,36 @@ private void methodWithAnnotation(SimpleFoo payload) {}
private void methodWithoutAnnotation() {}
}

private static class ClassImplementingInterface implements ClassInterface<String> {

@AsyncListener(
operation =
@AsyncOperation(
channelName = "test-channel",
description = "test channel operation description"))
@Override
public void methodFromInterface(String payload) {}
}

interface ClassInterface<T> {
void methodFromInterface(T payload);
}

private static class ClassImplementingInterfaceWithAnnotation implements ClassInterfaceWithAnnotation<String> {

@Override
public void methodFromInterface(String payload) {}
}

interface ClassInterfaceWithAnnotation<T> {
@AsyncListener(
operation =
@AsyncOperation(
channelName = "test-channel",
description = "test channel operation description"))
void methodFromInterface(T payload);
}

@Data
@NoArgsConstructor
@Schema(description = "SimpleFoo Message Description")
Expand Down
Loading