From c5f6c44fe9e13c506a40a6bd17ba989c8144f0b0 Mon Sep 17 00:00:00 2001 From: Silvar Yako Date: Sun, 13 Apr 2025 12:01:19 +0200 Subject: [PATCH] feat: support for azure service bus --- build.gradle | 1 + gradle/libs.versions.toml | 9 ++- settings.gradle | 3 + .../springwolf-asb-example/.env | 1 + .../springwolf-asb-example/build.gradle | 35 +++++++++ .../asb/SpringwolfAsbExampleApplication.java | 13 ++++ .../src/main/resources/application.properties | 21 ++++++ .../springwolf-asb-plugin/build.gradle | 72 +++++++++++++++++++ .../SpringwolfAsbAutoConfiguration.java | 22 ++++++ .../SpringwolfAsbConfigConstants.java | 11 +++ .../SpringwolfAsbConfigurationProperties.java | 27 +++++++ .../asb/asyncapi/SpringwolfAsbController.java | 36 ++++++++++ .../asb/asyncapi/SpringwolfAsbProducer.java | 35 +++++++++ .../SpringwolfAsbProducerConfiguration.java | 42 +++++++++++ .../SpringwolfAsbPropertiesConfiguration.java | 19 +++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + 16 files changed, 345 insertions(+), 3 deletions(-) create mode 100644 springwolf-examples/springwolf-asb-example/.env create mode 100644 springwolf-examples/springwolf-asb-example/build.gradle create mode 100644 springwolf-examples/springwolf-asb-example/src/main/java/io/github/springwolf/examples/asb/SpringwolfAsbExampleApplication.java create mode 100644 springwolf-examples/springwolf-asb-example/src/main/resources/application.properties create mode 100644 springwolf-plugins/springwolf-asb-plugin/build.gradle create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbAutoConfiguration.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigConstants.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigurationProperties.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbController.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducer.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducerConfiguration.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbPropertiesConfiguration.java create mode 100644 springwolf-plugins/springwolf-asb-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/build.gradle b/build.gradle index b6dcf2c0a..28aa1aaa4 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ var bindings = [ ] var plugins = [ 'springwolf-amqp', + 'springwolf-asb', 'springwolf-cloud-stream', 'springwolf-jms', 'springwolf-kafka', diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1ab3d9079..a2226a486 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,11 +7,11 @@ io-awspring-cloud = "3.3.0" io-confluent = "7.9.0" io-swagger-core-v3 = "2.2.30" jackson-core = "2.18.3" -kotlin = "2.1.20" +kotlin = "2.1.20" node-plugin = "7.1.0" nodeVersion = "22.12.0" npm = "10.9.0" -openapi-gradle = "1.9.0" +openapi-gradle = "1.9.0" org-junit-jupiter = "5.12.1" org-junit-plattform = "1.12.1" org-mockito = "5.17.0" @@ -24,10 +24,13 @@ spotless = "7.0.3" spring-boot-plugin = "3.4.4" spring-cloud = "2024.0.1" # not managed by spring-boot-dependencies spring-cloud-stream = "4.2.1" # not managed by spring-boot-dependencies - +azure-messaging-servicebus = "7.17.10" +azure-service-bus-starter="5.20.1" [libraries] activemq-broker = { module = "org.apache.activemq:activemq-broker", version.ref = "activemq" } amqp-client = "com.rabbitmq:amqp-client:5.25.0" +azure-messaging-service-bus = { module = "com.azure:azure-messaging-servicebus", version.ref = "azure-messaging-servicebus" } +azure-service-bus-starter = {module = "com.azure.spring:spring-cloud-azure-starter-servicebus", version.ref = "azure-service-bus-starter"} assertj-core = "org.assertj:assertj-core:3.27.3" avro = "org.apache.avro:avro:1.12.0" awaitility = "org.awaitility:awaitility:4.3.0" diff --git a/settings.gradle b/settings.gradle index ea9aab093..1e1a6b150 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include( 'springwolf-asyncapi', 'springwolf-core', 'springwolf-plugins:springwolf-amqp-plugin', + 'springwolf-plugins:springwolf-asb-plugin', 'springwolf-plugins:springwolf-cloud-stream-plugin', 'springwolf-plugins:springwolf-jms-plugin', 'springwolf-plugins:springwolf-kafka-plugin', @@ -12,6 +13,7 @@ include( 'springwolf-plugins:springwolf-stomp-plugin', 'springwolf-examples:e2e', 'springwolf-examples:springwolf-amqp-example', + 'springwolf-examples:springwolf-asb-example', 'springwolf-examples:springwolf-cloud-stream-example', 'springwolf-examples:springwolf-jms-example', 'springwolf-examples:springwolf-kafka-example', @@ -33,6 +35,7 @@ include( ) project(':springwolf-plugins:springwolf-amqp-plugin').name = 'springwolf-amqp' +project(':springwolf-plugins:springwolf-asb-plugin').name = 'springwolf-asb' project(':springwolf-plugins:springwolf-cloud-stream-plugin').name = 'springwolf-cloud-stream' project(':springwolf-plugins:springwolf-jms-plugin').name = 'springwolf-jms' project(':springwolf-plugins:springwolf-kafka-plugin').name = 'springwolf-kafka' diff --git a/springwolf-examples/springwolf-asb-example/.env b/springwolf-examples/springwolf-asb-example/.env new file mode 100644 index 000000000..8af56224a --- /dev/null +++ b/springwolf-examples/springwolf-asb-example/.env @@ -0,0 +1 @@ +SPRINGWOLF_VERSION=1.13.0-SNAPSHOT diff --git a/springwolf-examples/springwolf-asb-example/build.gradle b/springwolf-examples/springwolf-asb-example/build.gradle new file mode 100644 index 000000000..c2696a247 --- /dev/null +++ b/springwolf-examples/springwolf-asb-example/build.gradle @@ -0,0 +1,35 @@ +import org.springframework.boot.gradle.plugin.SpringBootPlugin + +plugins { + id 'java' + + alias libs.plugins.spring.boot +// alias libs.plugins.cutterslade.analyze + + alias libs.plugins.docker.spring.boot +} + +dependencies { + implementation project(":springwolf-core") + implementation project(":springwolf-plugins:springwolf-asb") +// permitUnusedDeclared project(":springwolf-plugins:springwolf-asb") + +// annotationProcessor project(":springwolf-plugins:springwolf-amqp") + runtimeOnly project(":springwolf-ui") + + annotationProcessor libs.lombok + compileOnly libs.lombok + + runtimeOnly libs.spring.boot.starter.web + + implementation libs.azure.service.bus.starter + + implementation libs.slf4j.api + implementation libs.swagger.annotations.jakarta + + implementation platform(SpringBootPlugin.BOM_COORDINATES) + implementation libs.spring.boot.autoconfigure + implementation libs.spring.boot + implementation libs.spring.context +} + diff --git a/springwolf-examples/springwolf-asb-example/src/main/java/io/github/springwolf/examples/asb/SpringwolfAsbExampleApplication.java b/springwolf-examples/springwolf-asb-example/src/main/java/io/github/springwolf/examples/asb/SpringwolfAsbExampleApplication.java new file mode 100644 index 000000000..13b0dcc5b --- /dev/null +++ b/springwolf-examples/springwolf-asb-example/src/main/java/io/github/springwolf/examples/asb/SpringwolfAsbExampleApplication.java @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.examples.asb; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringwolfAsbExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringwolfAsbExampleApplication.class, args); + } +} diff --git a/springwolf-examples/springwolf-asb-example/src/main/resources/application.properties b/springwolf-examples/springwolf-asb-example/src/main/resources/application.properties new file mode 100644 index 000000000..0ec486891 --- /dev/null +++ b/springwolf-examples/springwolf-asb-example/src/main/resources/application.properties @@ -0,0 +1,21 @@ +######### +# Spring configuration +spring.application.name=Springwolf example project - Azure Service Bus (ASB) + + +######### +# Spring azure service bus configuration +spring.cloud.azure.servicebus.enabled=true +spring.cloud.azure.servicebus.connection-string=Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true; +spring.cloud.azure.servicebus.producer.entity-name=test +spring.cloud.azure.servicebus.producer.entity-type=queue + + +######### +# Springwolf configuration +springwolf.enabled=true +springwolf.docket.base-package=io.github.springwolf.examples.asb + + +springwolf.plugin.asb.publishing.enabled=true + diff --git a/springwolf-plugins/springwolf-asb-plugin/build.gradle b/springwolf-plugins/springwolf-asb-plugin/build.gradle new file mode 100644 index 000000000..ccde7e358 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/build.gradle @@ -0,0 +1,72 @@ +import org.springframework.boot.gradle.plugin.SpringBootPlugin + +plugins { + id 'java-library' + + alias libs.plugins.spring.boot +// alias libs.plugins.cutterslade.analyze +} + +dependencies { + api project(":springwolf-core") + api project(":springwolf-asyncapi") + api project(":springwolf-bindings:springwolf-amqp-binding") +// permitUnusedDeclared project(":springwolf-bindings:springwolf-amqp-binding") + + implementation libs.slf4j.api + + annotationProcessor platform(SpringBootPlugin.BOM_COORDINATES) + implementation platform(SpringBootPlugin.BOM_COORDINATES) + implementation libs.spring.context + implementation libs.spring.core + implementation libs.spring.web + implementation libs.spring.boot + implementation libs.spring.boot.autoconfigure + implementation libs.azure.messaging.service.bus + + annotationProcessor libs.lombok + compileOnly libs.lombok + + implementation libs.jakarta.annotation.api + + annotationProcessor libs.spring.boot.configuration.processor + + testAnnotationProcessor libs.lombok + + testImplementation libs.assertj.core + + testImplementation libs.mockito.core + testImplementation libs.jackson.databind + + testImplementation libs.spring.boot.test + testImplementation libs.spring.beans + testImplementation libs.spring.test + + testAnnotationProcessor libs.lombok + + testImplementation libs.junit.jupiter.api + testRuntimeOnly libs.junit.jupiter + testRuntimeOnly libs.junit.plattform.launcher +} + +jar { + enabled = true + archiveClassifier = '' +} +bootJar.enabled = false + +java { + withJavadocJar() + withSourcesJar() +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + name = 'springwolf-asb' + description = 'Automated JSON API documentation for AMQP (Azure Service Bus) Listeners built with Spring' + } + } + } +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbAutoConfiguration.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbAutoConfiguration.java new file mode 100644 index 000000000..8790e5d72 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbAutoConfiguration.java @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.asb.asyncapi; + +import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Import; + +/** + * SpringwolfAsbAutoConfiguration for the springwolf asb plugin. + */ + +@AutoConfiguration +@ConditionalOnProperty(name = SpringwolfConfigConstants.SPRINGWOLF_ENABLED, havingValue = "true", matchIfMissing = true) +@Import({ + SpringwolfAsbPropertiesConfiguration.class, + SpringwolfAsbProducerConfiguration.class +}) +public class SpringwolfAsbAutoConfiguration { + + +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigConstants.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigConstants.java new file mode 100644 index 000000000..c80b6dec1 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigConstants.java @@ -0,0 +1,11 @@ +package io.github.springwolf.plugins.asb.asyncapi; + +import static io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants.SPRINGWOLF_PLUGIN_CONFIG_PREFIX; + +/** + * SpringwolfAsbConfigConstants. + */ +public class SpringwolfAsbConfigConstants { + public static final String SPRINGWOLF_AMQP_CONFIG_PREFIX = SPRINGWOLF_PLUGIN_CONFIG_PREFIX + ".asb"; + public static final String SPRINGWOLF_ASB_PLUGIN_PUBLISHING_ENABLED = "publishing.enabled"; +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigurationProperties.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigurationProperties.java new file mode 100644 index 000000000..c548355d9 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbConfigurationProperties.java @@ -0,0 +1,27 @@ +package io.github.springwolf.plugins.asb.asyncapi; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * SpringwolfAsbConfigurationProperties. + */ + +@ConfigurationProperties(prefix = SpringwolfAsbConfigConstants.SPRINGWOLF_AMQP_CONFIG_PREFIX) +@Getter +@Setter +public class SpringwolfAsbConfigurationProperties { + + private Publishing publishing; + + @Getter + @Setter + public static class Publishing { + + /** + * Enables/Disables the possibility to publish messages through springwolf on the configured amqp instance. + */ + private boolean enabled = false; + } +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbController.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbController.java new file mode 100644 index 000000000..db601e634 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbController.java @@ -0,0 +1,36 @@ +package io.github.springwolf.plugins.asb.asyncapi; + +import io.github.springwolf.core.controller.PublishingBaseController; +import io.github.springwolf.core.controller.PublishingPayloadCreator; +import io.github.springwolf.core.controller.dtos.MessageDto; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * SpringwolfAsbController. + */ + +@RestController +@RequestMapping("${springwolf.path.base:/springwolf}/asb") +public class SpringwolfAsbController extends PublishingBaseController { + + private final SpringwolfAsbProducer springwolfAsbProducer; + + public SpringwolfAsbController( + final PublishingPayloadCreator publishingPayloadCreator, + final SpringwolfAsbProducer springwolfAsbProducer + ) { + super(publishingPayloadCreator); + this.springwolfAsbProducer = springwolfAsbProducer; + } + + @Override + protected boolean isEnabled() { + return springwolfAsbProducer.isEnabled(); + } + + @Override + protected void publishMessage(final String topic, final MessageDto message, final Object payload) { + springwolfAsbProducer.send(topic, message); + } +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducer.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducer.java new file mode 100644 index 000000000..3aed38a47 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducer.java @@ -0,0 +1,35 @@ +package io.github.springwolf.plugins.asb.asyncapi; + + +import com.azure.core.util.BinaryData; +import com.azure.messaging.servicebus.ServiceBusMessage; +import com.azure.messaging.servicebus.ServiceBusSenderAsyncClient; +import io.github.springwolf.core.asyncapi.AsyncApiService; + +import java.util.List; +import java.util.Optional; + +/** + * SpringwolfAsbProducer. + */ +public class SpringwolfAsbProducer { + + private final AsyncApiService asyncApiService; + private final Optional senderAsyncClient; + + public SpringwolfAsbProducer(final AsyncApiService asyncApiService, final List senderAsyncClient) { + this.asyncApiService = asyncApiService; + this.senderAsyncClient = senderAsyncClient.isEmpty() ? Optional.empty() : Optional.of(senderAsyncClient.getFirst()); + } + + public boolean isEnabled() { + return senderAsyncClient.isPresent(); + } + + public void send(String channelName, Object payload) { + if (senderAsyncClient.isEmpty()) return; + + ServiceBusMessage serviceBusMessage = new ServiceBusMessage(BinaryData.fromObject(payload)); + senderAsyncClient.get().sendMessage(serviceBusMessage).block(); + } +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducerConfiguration.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducerConfiguration.java new file mode 100644 index 000000000..f3d8679ec --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbProducerConfiguration.java @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.asb.asyncapi; + +import com.azure.messaging.servicebus.ServiceBusSenderAsyncClient; +import io.github.springwolf.core.asyncapi.AsyncApiService; +import io.github.springwolf.core.controller.PublishingPayloadCreator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; + +import java.util.List; + +import static io.github.springwolf.plugins.asb.asyncapi.SpringwolfAsbConfigConstants.*; + + +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty( + prefix = SPRINGWOLF_AMQP_CONFIG_PREFIX, + name = SPRINGWOLF_ASB_PLUGIN_PUBLISHING_ENABLED, + havingValue = "true") +public class SpringwolfAsbProducerConfiguration { + + @Bean + @ConditionalOnMissingBean + public SpringwolfAsbProducer springwolfAmqpProducer( + AsyncApiService asyncApiService, + @NonNull List senderAsyncClients + ) { + return new SpringwolfAsbProducer(asyncApiService, senderAsyncClients); + } + + @Bean + @ConditionalOnMissingBean + public SpringwolfAsbController springwolfAmqpController( + PublishingPayloadCreator publishingPayloadCreator, + SpringwolfAsbProducer springwolfAsbProducer + ) { + return new SpringwolfAsbController(publishingPayloadCreator, springwolfAsbProducer); + } +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbPropertiesConfiguration.java b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbPropertiesConfiguration.java new file mode 100644 index 000000000..673852d7f --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/java/io/github/springwolf/plugins/asb/asyncapi/SpringwolfAsbPropertiesConfiguration.java @@ -0,0 +1,19 @@ +package io.github.springwolf.plugins.asb.asyncapi; + +import io.github.springwolf.core.standalone.StandaloneConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * SpringwolfAsbPropertiesConfiguration. + */ + +@Configuration(proxyBeanMethods = false) +@StandaloneConfiguration +public class SpringwolfAsbPropertiesConfiguration { + + @Bean + public SpringwolfAsbConfigurationProperties springwolfAsbConfigurationProperties(){ + return new SpringwolfAsbConfigurationProperties(); + } +} diff --git a/springwolf-plugins/springwolf-asb-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springwolf-plugins/springwolf-asb-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..2cf656103 --- /dev/null +++ b/springwolf-plugins/springwolf-asb-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.springwolf.plugins.asb.asyncapi.SpringwolfAsbAutoConfiguration