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

(fix): Google pubsub protocol schema setting - cloudsteam #652

Merged
merged 11 commits into from
Mar 22, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/springwolf-bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
binding: [ "sns", "sqs" ]
binding: [ "googlepubsub", "sns", "sqs" ]

env:
binding: springwolf-bindings/springwolf-${{ matrix.binding }}-binding
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include(
'springwolf-examples:springwolf-sqs-example',
'springwolf-bindings:springwolf-sns-binding',
'springwolf-bindings:springwolf-sqs-binding',
'springwolf-bindings:springwolf-googlepubsub-binding',
'springwolf-ui',
'springwolf-add-ons:springwolf-common-model-converters',
'springwolf-add-ons:springwolf-generic-binding',
Expand All @@ -29,3 +30,4 @@ project(':springwolf-plugins:springwolf-jms-plugin').name = 'springwolf-jms'
project(':springwolf-plugins:springwolf-kafka-plugin').name = 'springwolf-kafka'
project(':springwolf-plugins:springwolf-sns-plugin').name = 'springwolf-sns'
project(':springwolf-plugins:springwolf-sqs-plugin').name = 'springwolf-sqs'

45 changes: 45 additions & 0 deletions springwolf-bindings/springwolf-googlepubsub-binding/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
id 'java-library'

id 'org.springframework.boot'
id 'io.spring.dependency-management'
id 'ca.cutterslade.analyze'
}

dependencies {
api project(":springwolf-asyncapi")
api project(":springwolf-core")

implementation "org.springframework:spring-context"
implementation "org.springframework:spring-core"
implementation "org.springframework.boot:spring-boot-autoconfigure"

implementation "org.apache.commons:commons-lang3:${commonsLang3Version}"

testImplementation "org.assertj:assertj-core:${assertjCoreVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"

testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
}

jar {
enabled = true
archiveClassifier = ''
}
bootJar.enabled = false

java {
withJavadocJar()
withSourcesJar()
}

publishing {
publications {
mavenJava(MavenPublication) {
pom {
name = 'springwolf-googlepubsub-binding'
description = 'Automated JSON API documentation for Google PubSub Bindings'
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.bindings.googlepubsub.annotations;

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;

/**
* {@code @GooglePubSubAsyncChannelBinding} is a method-level annotation.
* It configures the channel binding for the Google pubsub protocol.
* @see io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubChannelBinding
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Inherited
public @interface GooglePubSubAsyncChannelBinding {
String type() default "googlepubsub";

String messageRetentionDuration() default "";

GooglePubsubAsyncMessageStoragePolicy messageStoragePolicy() default @GooglePubsubAsyncMessageStoragePolicy;

GooglePubSubAsyncSchemaSetting schemaSettings();

String bindingVersion() default "0.2.0";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.bindings.googlepubsub.annotations;

import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubSchemaSettings;

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;

/**
* @see GooglePubSubSchemaSettings
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Inherited
public @interface GooglePubSubAsyncSchemaSetting {
/**
* Mapped to {@link GooglePubSubSchemaSettings#getEncoding()}
*/
String encoding() default "";

/**
* Mapped to {@link GooglePubSubSchemaSettings#getName()}
*/
String name() default "";

/**
* Mapped to {@link GooglePubSubSchemaSettings#getFirstRevisionId()}
*/
String firstRevisionId() default "";

/**
* Mapped to {@link GooglePubSubSchemaSettings#getLastRevisionId()}
*/
String lastRevisionId() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.bindings.googlepubsub.annotations;

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, ElementType.ANNOTATION_TYPE})
@Inherited
public @interface GooglePubsubAsyncMessageStoragePolicy {
String[] allowedPersistenceRegions() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.bindings.googlepubsub.configuration;

import io.github.springwolf.bindings.googlepubsub.scanners.channels.GooglePubSubChannelBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingProcessorPriority;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;

/**
* Autoconfiguration for the springwolf Google PubSub Binding.
*/
@AutoConfiguration
@ConditionalOnProperty(name = SpringwolfConfigConstants.SPRINGWOLF_ENABLED, havingValue = "true", matchIfMissing = true)
public class SpringwolfGooglePubSubBindingAutoConfiguration {

@Bean
@Order(value = BindingProcessorPriority.PROTOCOL_BINDING)
@ConditionalOnMissingBean
public GooglePubSubChannelBindingProcessor googlePubSubChannelBindingProcessor() {
return new GooglePubSubChannelBindingProcessor();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.bindings.googlepubsub.scanners.channels;

import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubChannelBinding;
import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubMessageStoragePolicy;
import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubSchemaSettings;
import io.github.springwolf.bindings.googlepubsub.annotations.GooglePubSubAsyncChannelBinding;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ChannelBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ProcessedChannelBinding;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.StringValueResolver;

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

public class GooglePubSubChannelBindingProcessor implements ChannelBindingProcessor, EmbeddedValueResolverAware {
private StringValueResolver resolver;

@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
}

@Override
public Optional<ProcessedChannelBinding> process(Method method) {
return Arrays.stream(method.getAnnotations())
.filter(GooglePubSubAsyncChannelBinding.class::isInstance)
.map(GooglePubSubAsyncChannelBinding.class::cast)
.findAny()
.map(this::mapToChannelBinding);
}

private ProcessedChannelBinding mapToChannelBinding(GooglePubSubAsyncChannelBinding bindingAnnotation) {

GooglePubSubMessageStoragePolicy.GooglePubSubMessageStoragePolicyBuilder policyBuilder =
GooglePubSubMessageStoragePolicy.builder();
if (bindingAnnotation.messageStoragePolicy().allowedPersistenceRegions().length > 0) {
policyBuilder.allowedPersistenceRegions(
Arrays.stream(bindingAnnotation.messageStoragePolicy().allowedPersistenceRegions())
.toList());
}

GooglePubSubSchemaSettings.GooglePubSubSchemaSettingsBuilder schemaSettingsBuilder =
GooglePubSubSchemaSettings.builder();
if (StringUtils.isNotBlank(bindingAnnotation.schemaSettings().encoding())) {
schemaSettingsBuilder.encoding(bindingAnnotation.schemaSettings().encoding());
}
if (StringUtils.isNotBlank(bindingAnnotation.schemaSettings().firstRevisionId())) {
schemaSettingsBuilder.firstRevisionId(
bindingAnnotation.schemaSettings().firstRevisionId());
}
if (StringUtils.isNotBlank(bindingAnnotation.schemaSettings().lastRevisionId())) {
schemaSettingsBuilder.lastRevisionId(
bindingAnnotation.schemaSettings().lastRevisionId());
}
if (StringUtils.isNotBlank(bindingAnnotation.schemaSettings().name())) {
schemaSettingsBuilder.name(bindingAnnotation.schemaSettings().name());
}

GooglePubSubChannelBinding.GooglePubSubChannelBindingBuilder bindingBuilder =
GooglePubSubChannelBinding.builder()
.messageStoragePolicy(policyBuilder.build())
.schemaSettings(schemaSettingsBuilder.build());
if (StringUtils.isNotBlank(bindingAnnotation.messageRetentionDuration())) {
bindingBuilder.messageRetentionDuration(bindingAnnotation.messageRetentionDuration());
}
if (StringUtils.isNotBlank(bindingAnnotation.bindingVersion())) {
bindingBuilder.bindingVersion(bindingAnnotation.bindingVersion());
}

return new ProcessedChannelBinding(bindingAnnotation.type(), bindingBuilder.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.springwolf.bindings.googlepubsub.configuration.SpringwolfGooglePubSubBindingAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Apache-2.0
import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubChannelBinding;
import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubMessageStoragePolicy;
import io.github.springwolf.asyncapi.v3.bindings.googlepubsub.GooglePubSubSchemaSettings;
import io.github.springwolf.bindings.googlepubsub.annotations.GooglePubSubAsyncChannelBinding;
import io.github.springwolf.bindings.googlepubsub.annotations.GooglePubSubAsyncSchemaSetting;
import io.github.springwolf.bindings.googlepubsub.annotations.GooglePubsubAsyncMessageStoragePolicy;
import io.github.springwolf.bindings.googlepubsub.scanners.channels.GooglePubSubChannelBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ProcessedChannelBinding;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.assertThat;

public class GooglePubSubChannelBindingProcessorTest {
private final GooglePubSubChannelBindingProcessor processor = new GooglePubSubChannelBindingProcessor();

@Test
void processTest() throws NoSuchMethodException {
// given
Method method = GooglePubSubChannelBindingProcessorTest.class.getMethod("methodWithAnnotation");

// when
ProcessedChannelBinding binding = processor.process(method).get();

// then
assertThat(binding.getType()).isEqualTo("googlepubsub");
assertThat(binding.getBinding())
.isEqualTo(new GooglePubSubChannelBinding(
null,
"messageRetentionDuration",
new GooglePubSubMessageStoragePolicy(List.of("region1", "region2")),
new GooglePubSubSchemaSettings("BINARY", "firstRevisionId", "lastRevisionId", "project/test"),
"0.2.0"));
}

@Test
void processWithoutAnnotationTest() throws NoSuchMethodException {
// given
Method method = GooglePubSubChannelBindingProcessorTest.class.getMethod("methodWithoutAnnotation");

// when
Optional<ProcessedChannelBinding> binding = processor.process(method);

// then
assertThat(binding).isNotPresent();
}

@GooglePubSubAsyncChannelBinding(
messageRetentionDuration = "messageRetentionDuration",
messageStoragePolicy =
@GooglePubsubAsyncMessageStoragePolicy(allowedPersistenceRegions = {"region1", "region2"}),
schemaSettings =
@GooglePubSubAsyncSchemaSetting(
encoding = "BINARY",
firstRevisionId = "firstRevisionId",
lastRevisionId = "lastRevisionId",
name = "project/test"))
public void methodWithAnnotation() {}

public void methodWithoutAnnotation() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.bindings.channels;

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

public interface ChannelBindingProcessor {

/**
* Process the methods annotated with Channel Binding Annotation
* for protocol specific channelBinding annotations, method parameters, etc
*
* @param method The method being annotated
* @return A message binding, if found
*/
Optional<ProcessedChannelBinding> process(Method method);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.bindings.channels;

import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding;
import lombok.Data;

@Data
public class ProcessedChannelBinding {
private final String type;
private final ChannelBinding binding;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.common.utils;

import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding;
import io.github.springwolf.asyncapi.v3.bindings.MessageBinding;
import io.github.springwolf.asyncapi.v3.bindings.OperationBinding;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.core.asyncapi.annotations.AsyncMessage;
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaderSchema;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaders;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ChannelBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ProcessedChannelBinding;
import io.github.springwolf.core.asyncapi.scanners.bindings.messages.MessageBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.messages.ProcessedMessageBinding;
import io.github.springwolf.core.asyncapi.scanners.bindings.operations.OperationBindingProcessor;
Expand Down Expand Up @@ -136,4 +139,14 @@ public static void processAsyncMessageAnnotation(
public static List<String> getServers(AsyncOperation op, StringValueResolver resolver) {
return Arrays.stream(op.servers()).map(resolver::resolveStringValue).toList();
}

public static Map<String, ChannelBinding> processChannelBindingFromAnnotation(
Method method, List<ChannelBindingProcessor> channelBindingProcessors) {
return channelBindingProcessors.stream()
.map(channelBindingProcessor -> channelBindingProcessor.process(method))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toMap(
ProcessedChannelBinding::getType, ProcessedChannelBinding::getBinding, (e1, e2) -> e1));
}
}
Loading