Skip to content

Commit 8ea2c8f

Browse files
authored
Feature/spring cloud streams (#115)
* Simplify component scanning * Add support for cloud stream functions
1 parent 514fa67 commit 8ea2c8f

File tree

57 files changed

+1831
-541
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1831
-541
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: springwolf-cloud-stream
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- '.github/workflows/springwolf-cloud-stream.yml'
9+
- 'springwolf-core/**'
10+
- 'springwolf-plugins/springwolf-cloud-stream-plugin/**'
11+
- 'springwolf-examples/springwolf-cloud-stream-example/**'
12+
pull_request:
13+
types: [ opened, synchronize, ready_for_review ]
14+
15+
jobs:
16+
build:
17+
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v2
22+
23+
- name: Setup spectral
24+
run: |
25+
echo 'extends: ["spectral:asyncapi"]' > .spectral.yaml
26+
- name: Lint asyncapi.json with spectral
27+
uses: stoplightio/spectral-action@v0.8.8
28+
with:
29+
file_glob: 'springwolf-examples/springwolf-cloud-stream-example/src/test/resources/asyncapi.json'
30+
31+
- name: Set up JDK 1.8
32+
uses: actions/setup-java@v1
33+
with:
34+
java-version: 1.8
35+
36+
- name: Setup Gradle
37+
uses: gradle/gradle-build-action@v2
38+
39+
- name: Run unit tests
40+
run: ./gradlew -p springwolf-plugins/springwolf-cloud-stream-plugin test
41+
42+
- name: Run integration tests
43+
run: ./gradlew -p springwolf-examples/springwolf-cloud-stream-example test
44+
45+
- name: Publish package
46+
if: github.ref == 'refs/heads/master'
47+
run: ./gradlew publish -p springwolf-plugins/springwolf-cloud-stream-plugin
48+
env:
49+
ORG_GRADLE_PROJECT_SNAPSHOT: true
50+
51+
ORG_GRADLE_PROJECT_SIGNINGKEY: ${{secrets.ORG_GRADLE_PROJECT_SIGNINGKEY}}
52+
ORG_GRADLE_PROJECT_SIGNINGPASSWORD: ${{secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD}}
53+
54+
ORG_GRADLE_PROJECT_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
55+
ORG_GRADLE_PROJECT_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}

settings.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ include(
44
'springwolf-core',
55
'springwolf-plugins:springwolf-amqp-plugin',
66
'springwolf-plugins:springwolf-kafka-plugin',
7+
'springwolf-plugins:springwolf-cloud-stream-plugin',
78
'springwolf-examples:springwolf-kafka-example',
89
'springwolf-examples:springwolf-amqp-example',
10+
'springwolf-examples:springwolf-cloud-stream-example',
911
'springwolf-add-ons:springwolf-common-model-converters'
1012
)
1113

springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
1010
import com.fasterxml.jackson.databind.ObjectMapper;
1111
import com.fasterxml.jackson.databind.module.SimpleModule;
12+
import io.github.stavshamir.springwolf.asyncapi.serializers.EmptyChannelBindingSerializer;
13+
import io.github.stavshamir.springwolf.asyncapi.serializers.EmptyOperationBindingSerializer;
1214
import io.github.stavshamir.springwolf.asyncapi.serializers.KafkaChannelBindingSerializer;
1315
import io.github.stavshamir.springwolf.asyncapi.serializers.KafkaOperationBindingSerializer;
1416
import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI;
17+
import io.github.stavshamir.springwolf.asyncapi.types.channel.bindings.EmptyChannelBinding;
18+
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.bindings.EmptyOperationBinding;
1519
import org.springframework.stereotype.Service;
1620

1721
import javax.annotation.PostConstruct;
@@ -30,6 +34,8 @@ void postConstruct() {
3034

3135
private void registerKafkaOperationBindingSerializer() {
3236
SimpleModule module = new SimpleModule();
37+
module.addSerializer(EmptyChannelBinding.class, new EmptyChannelBindingSerializer());
38+
module.addSerializer(EmptyOperationBinding.class, new EmptyOperationBindingSerializer());
3339
module.addSerializer(KafkaChannelBinding.class, new KafkaChannelBindingSerializer());
3440
module.addSerializer(KafkaOperationBinding.class, new KafkaOperationBindingSerializer());
3541
jsonMapper.registerModule(module);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.github.stavshamir.springwolf.asyncapi.scanners.beans;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.Set;
5+
6+
public interface BeanMethodsScanner {
7+
8+
/**
9+
* @return Methods annotated with @Bean which are declared in @Configuration classes from the base package.
10+
*/
11+
Set<Method> getBeanMethods();
12+
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.github.stavshamir.springwolf.asyncapi.scanners.beans;
2+
3+
import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ConfigurationClassScanner;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.lang.reflect.Method;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
import java.util.Set;
12+
import java.util.stream.Stream;
13+
14+
import static java.util.stream.Collectors.toSet;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
public class DefaultBeanMethodsScanner implements BeanMethodsScanner {
19+
20+
private final ConfigurationClassScanner configurationClassScanner;
21+
22+
@Override
23+
public Set<Method> getBeanMethods() {
24+
Set<Class<?>> configurationClasses = configurationClassScanner.scan();
25+
26+
Stream<Method> methods = configurationClasses.stream()
27+
.map(Class::getDeclaredMethods)
28+
.map(Arrays::asList)
29+
.flatMap(List::stream);
30+
31+
return methods
32+
.filter(method -> method.isAnnotationPresent(Bean.class))
33+
.collect(toSet());
34+
}
35+
36+
}

springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AbstractClassLevelListenerScanner.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
import com.google.common.collect.Maps;
88
import io.github.stavshamir.springwolf.asyncapi.MessageHelper;
99
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner;
10+
import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ComponentClassScanner;
1011
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message;
1112
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference;
1213
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders;
1314
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference;
14-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocket;
1515
import io.github.stavshamir.springwolf.schemas.SchemasService;
1616
import lombok.extern.slf4j.Slf4j;
1717
import org.springframework.beans.factory.annotation.Autowired;
@@ -30,7 +30,7 @@
3030
public abstract class AbstractClassLevelListenerScanner<ClassAnnotation extends Annotation, MethodAnnotation extends Annotation> implements ChannelsScanner {
3131

3232
@Autowired
33-
private AsyncApiDocket docket;
33+
private ComponentClassScanner componentClassScanner;
3434

3535
@Autowired
3636
private SchemasService schemasService;
@@ -40,11 +40,14 @@ public abstract class AbstractClassLevelListenerScanner<ClassAnnotation extends
4040

4141
/**
4242
* This annotation is used on class level
43+
*
4344
* @return The class object of the listener annotation.
4445
*/
4546
protected abstract Class<ClassAnnotation> getListenerAnnotationClass();
47+
4648
/**
4749
* This annotation is used on the method level
50+
*
4851
* @return The class object of the handler annotation.
4952
*/
5053
protected abstract Class<MethodAnnotation> getHandlerAnnotationClass();
@@ -69,6 +72,7 @@ public abstract class AbstractClassLevelListenerScanner<ClassAnnotation extends
6972

7073
/**
7174
* Can be overriden by implementations
75+
*
7276
* @param method The specific method. Can be used to extract the payload type
7377
* @return The AsyncHeaders
7478
*/
@@ -78,7 +82,7 @@ protected AsyncHeaders buildHeaders(Method method) {
7882

7983
@Override
8084
public Map<String, ChannelItem> scan() {
81-
Set<Class<?>> components = docket.getComponentsScanner().scanForComponents();
85+
Set<Class<?>> components = componentClassScanner.scan();
8286
Set<Map.Entry<String, ChannelItem>> channels = mapToChannels(components);
8387
return mergeChannels(channels);
8488
}

springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AbstractMethodLevelListenerScanner.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import com.asyncapi.v2.model.channel.operation.Operation;
77
import com.google.common.collect.Maps;
88
import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner;
9+
import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ComponentClassScanner;
910
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message;
1011
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference;
1112
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders;
1213
import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference;
13-
import io.github.stavshamir.springwolf.configuration.AsyncApiDocket;
1414
import io.github.stavshamir.springwolf.schemas.SchemasService;
1515
import lombok.extern.slf4j.Slf4j;
1616
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,14 +26,14 @@
2626
public abstract class AbstractMethodLevelListenerScanner<T extends Annotation> implements ChannelsScanner {
2727

2828
@Autowired
29-
private AsyncApiDocket docket;
29+
private ComponentClassScanner componentClassScanner;
3030

3131
@Autowired
3232
private SchemasService schemasService;
3333

3434
@Override
3535
public Map<String, ChannelItem> scan() {
36-
return docket.getComponentsScanner().scanForComponents().stream()
36+
return componentClassScanner.scan().stream()
3737
.map(this::getAnnotatedMethods).flatMap(Collection::stream)
3838
.map(this::mapMethodToChannel)
3939
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.github.stavshamir.springwolf.asyncapi.scanners.classes;
2+
3+
import io.github.stavshamir.springwolf.configuration.AsyncApiDocket;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.apache.commons.lang3.StringUtils;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.beans.factory.config.BeanDefinition;
8+
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
9+
import org.springframework.core.type.filter.AnnotationTypeFilter;
10+
11+
import java.lang.annotation.Annotation;
12+
import java.util.Optional;
13+
import java.util.Set;
14+
15+
import static java.util.stream.Collectors.toSet;
16+
17+
@Slf4j
18+
public abstract class AbstractAnnotatedClassScanner<T extends Annotation> implements ClassScanner {
19+
20+
@Autowired
21+
private AsyncApiDocket docket;
22+
23+
/**
24+
* @return The class object of the annotation to scan.
25+
*/
26+
protected abstract Class<T> getAnnotationClass();
27+
28+
@Override
29+
public Set<Class<?>> scan() {
30+
String basePackage = docket.getBasePackage();
31+
if (StringUtils.isBlank(basePackage)) {
32+
throw new IllegalArgumentException("Base package must not be blank");
33+
}
34+
35+
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
36+
provider.addIncludeFilter(new AnnotationTypeFilter(getAnnotationClass()));
37+
38+
log.debug("Scanning for {} classes in {}", getAnnotationClass().getSimpleName(), basePackage);
39+
return provider.findCandidateComponents(basePackage).stream()
40+
.map(BeanDefinition::getBeanClassName)
41+
.map(this::getClass)
42+
.filter(Optional::isPresent)
43+
.map(Optional::get)
44+
.collect(toSet());
45+
}
46+
47+
private Optional<Class<?>> getClass(String className) {
48+
try {
49+
log.debug("Found candidate class: {}", className);
50+
return Optional.of(Class.forName(className));
51+
} catch (ClassNotFoundException e) {
52+
log.warn("Failed to get class for name: {}", className);
53+
return Optional.empty();
54+
}
55+
}
56+
57+
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
package io.github.stavshamir.springwolf.asyncapi.scanners.components;
1+
package io.github.stavshamir.springwolf.asyncapi.scanners.classes;
22

33
import java.util.Set;
44
import io.github.stavshamir.springwolf.configuration.AsyncApiDocket;
55

6-
public interface ComponentsScanner {
6+
public interface ClassScanner {
77

88
/**
99
* Scan your project for components potentially containing asynchronous listeners,
1010
* based on the configuration of your registered {@link AsyncApiDocket}.
1111
*
1212
* @return A set of found classes.
1313
*/
14-
Set<Class<?>> scanForComponents();
14+
Set<Class<?>> scan();
1515
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.github.stavshamir.springwolf.asyncapi.scanners.classes;
2+
3+
import org.springframework.stereotype.Component;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service
7+
public class ComponentClassScanner extends AbstractAnnotatedClassScanner<Component> implements ClassScanner {
8+
9+
@Override
10+
protected Class<Component> getAnnotationClass() {
11+
return Component.class;
12+
}
13+
14+
}

0 commit comments

Comments
 (0)