diff --git a/.all-contributorsrc b/.all-contributorsrc
index e49f22373..5eeba88d0 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -243,6 +243,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "krzysztofxkwiecien",
+ "name": "Krzysztof Kwiecień",
+ "avatar_url": "https://avatars.githubusercontent.com/u/37042650?v=4",
+ "profile": "https://github.com/krzysztofxkwiecien",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/.github/workflows/springwolf-plugins.yml b/.github/workflows/springwolf-plugins.yml
index ea0ab02e0..3a433ce27 100644
--- a/.github/workflows/springwolf-plugins.yml
+++ b/.github/workflows/springwolf-plugins.yml
@@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- plugin: [ "amqp", "cloud-stream", "kafka", "sns", "sqs" ]
+ plugin: [ "amqp", "cloud-stream", "jms", "kafka", "sns", "sqs" ]
env:
plugin: springwolf-plugins/springwolf-${{ matrix.plugin }}-plugin
diff --git a/README.md b/README.md
index 9f00b1c1b..f114bed47 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,7 @@ More details in the documentation.
| [AWS SNS](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-sns-plugin) | [AWS SNS Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-sns-example) |  |  |
| [AWS SQS](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-sqs-plugin) | [AWS SQS Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-sqs-example) |  |  |
| [Cloud Stream](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-cloud-stream-plugin) | [Cloud Stream Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-cloud-stream-example) |  |  |
+| [JMS](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-jms-plugin) | [JMS Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-jms-example) |  |  |
| [Kafka](https://github.com/springwolf/springwolf-core/tree/master/springwolf-plugins/springwolf-kafka-plugin) | [Kafka Example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-kafka-example) |  |  |
| [Common Model Converter](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-common-model-converters) | |  |  |
| [Generic Binding](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-generic-binding) | |  |  |
@@ -142,6 +143,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Sheheryar Aamir 💻 |
 jmwestbe 💻 |
 pdalfarr 💻 |
+  Krzysztof Kwiecień 💻 |
diff --git a/RELEASING.md b/RELEASING.md
index e59c53928..b596aa125 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -6,9 +6,10 @@ The following list describe the steps necessary to release a new version.
2. Run docker compose and manually test ui (or verify on website):
1. AMQP: https://amqp.demo.springwolf.dev/
2. CloudStream https://cloud-stream.demo.springwolf.dev/
- 3. Kafka: https://kafka.demo.springwolf.dev/
- 4. SNS: https://sns.demo.springwolf.dev/
- 5. SQS: https://sqs.demo.springwolf.dev/
+ 3. JMS: https://jms.demo.springwolf.dev/
+ 4. Kafka: https://kafka.demo.springwolf.dev/
+ 5. SNS: https://sns.demo.springwolf.dev/
+ 6. SQS: https://sqs.demo.springwolf.dev/
3. Update `all-contributors` in [README.md](README.md)
4. Remove the `-SNAPHSOT` postfix in `.env`, create a new branch `release/0.X.X` (version number), commit & push
5. Run GitHub `Publish releases` pipeline from the newly created release branch
diff --git a/build.gradle b/build.gradle
index 2fb305801..96832fce3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -117,6 +117,7 @@ allprojects {
project.name == 'springwolf-core' ||
project.name == 'springwolf-amqp' ||
project.name == 'springwolf-cloud-stream' ||
+ project.name == 'springwolf-jms' ||
project.name == 'springwolf-kafka' ||
project.name == 'springwolf-sns' ||
project.name == 'springwolf-sqs' ||
diff --git a/settings.gradle b/settings.gradle
index e1104c57e..ae0946379 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,11 +4,13 @@ include(
'springwolf-core',
'springwolf-plugins:springwolf-amqp-plugin',
'springwolf-plugins:springwolf-cloud-stream-plugin',
+ 'springwolf-plugins:springwolf-jms-plugin',
'springwolf-plugins:springwolf-kafka-plugin',
'springwolf-plugins:springwolf-sns-plugin',
'springwolf-plugins:springwolf-sqs-plugin',
'springwolf-examples:springwolf-amqp-example',
'springwolf-examples:springwolf-cloud-stream-example',
+ 'springwolf-examples:springwolf-jms-example',
'springwolf-examples:springwolf-kafka-example',
'springwolf-examples:springwolf-sns-example',
'springwolf-examples:springwolf-sqs-example',
@@ -20,6 +22,7 @@ include(
project(':springwolf-plugins:springwolf-amqp-plugin').name = 'springwolf-amqp'
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'
project(':springwolf-plugins:springwolf-sns-plugin').name = 'springwolf-sns'
project(':springwolf-plugins:springwolf-sqs-plugin').name = 'springwolf-sqs'
diff --git a/springwolf-examples/springwolf-jms-example/.env b/springwolf-examples/springwolf-jms-example/.env
new file mode 120000
index 000000000..c7360fb82
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/.env
@@ -0,0 +1 @@
+../../.env
\ No newline at end of file
diff --git a/springwolf-examples/springwolf-jms-example/README.md b/springwolf-examples/springwolf-jms-example/README.md
new file mode 100644
index 000000000..fc5e472fa
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/README.md
@@ -0,0 +1,6 @@
+## Usage
+
+### Run with docker compose (recommended)
+1. Copy the `docker-compose.yml` file to your machine.
+2. Run `$ docker-compose up`.
+3. Visit `localhost:8080/springwolf/asyncapi-ui.html` or try the API: `$ curl localhost:8080/springwolf/docs`.
diff --git a/springwolf-examples/springwolf-jms-example/build.gradle b/springwolf-examples/springwolf-jms-example/build.gradle
new file mode 100644
index 000000000..3118a410b
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id 'java'
+
+ id 'org.springframework.boot'
+ id 'io.spring.dependency-management'
+ id 'ca.cutterslade.analyze'
+
+ id 'com.bmuschko.docker-spring-boot-application'
+}
+
+dependencies {
+ implementation project(":springwolf-core")
+ implementation project(":springwolf-add-ons:springwolf-generic-binding")
+ implementation project(":springwolf-plugins:springwolf-jms")
+
+ annotationProcessor project(":springwolf-plugins:springwolf-jms")
+ runtimeOnly project(":springwolf-ui")
+
+ compileOnly "jakarta.jms:jakarta.jms-api"
+
+ implementation "org.slf4j:slf4j-api:${slf4jApiVersion}"
+
+ implementation "io.swagger.core.v3:swagger-annotations:${swaggerVersion}"
+
+ implementation "org.springframework:spring-context"
+ implementation "org.springframework:spring-jms"
+ implementation "org.springframework.boot:spring-boot"
+ implementation "org.springframework.boot:spring-boot-autoconfigure"
+ runtimeOnly "org.springframework.boot:spring-boot-starter-web"
+ runtimeOnly "org.springframework.boot:spring-boot-starter-artemis"
+
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
+
+ testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
+
+ testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}"
+
+ testImplementation "org.springframework.boot:spring-boot-test"
+ testImplementation "org.springframework:spring-beans"
+ testImplementation "org.springframework:spring-web"
+ testImplementation "org.springframework:spring-test"
+
+ testImplementation "org.testcontainers:testcontainers:${testcontainersVersion}"
+ testImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}"
+}
+
+docker {
+ springBootApplication {
+ maintainer = 'shamir.stav@gmail.com'
+ baseImage = 'eclipse-temurin:17-jre-focal'
+ ports = [8080]
+ images = ["stavshamir/springwolf-jms-example:${project.version}"]
+ }
+
+ registryCredentials {
+ username = project.findProperty('DOCKERHUB_USERNAME') ?: ''
+ password = project.findProperty('DOCKERHUB_TOKEN') ?: ''
+ }
+}
+
+test {
+ dependsOn dockerBuildImage
+}
diff --git a/springwolf-examples/springwolf-jms-example/docker-compose.yml b/springwolf-examples/springwolf-jms-example/docker-compose.yml
new file mode 100644
index 000000000..ecab0a081
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/docker-compose.yml
@@ -0,0 +1,20 @@
+version: '3'
+services:
+ app:
+ image: stavshamir/springwolf-jms-example:${SPRINGWOLF_VERSION}
+ links:
+ - activemq
+ environment:
+ BOOTSTRAP_SERVER: tcp://activemq:61616
+ ports:
+ - "8080:8080"
+ depends_on:
+ - activemq
+
+ activemq:
+ image: apache/activemq-artemis:2.31.2
+ environment:
+ EXTRA_ARGS: --http-host 0.0.0.0 --relax-jolokia --nio
+ ports:
+ - "61616:61616"
+ - "8161:8161"
diff --git a/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/SpringwolfJmsExampleApplication.java b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/SpringwolfJmsExampleApplication.java
new file mode 100644
index 000000000..68ba0328a
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/SpringwolfJmsExampleApplication.java
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringwolfJmsExampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringwolfJmsExampleApplication.class, args);
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/config/ConverterConfig.java b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/config/ConverterConfig.java
new file mode 100644
index 000000000..6b830d929
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/config/ConverterConfig.java
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
+import org.springframework.jms.support.converter.MessageConverter;
+import org.springframework.jms.support.converter.MessageType;
+
+@Configuration
+class ConverterConfig {
+
+ @Bean
+ public MessageConverter jacksonJmsMessageConverter() {
+ MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
+ converter.setTargetType(MessageType.TEXT);
+ converter.setTypeIdPropertyName("_type");
+ return converter;
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/consumers/ExampleConsumer.java b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/consumers/ExampleConsumer.java
new file mode 100644
index 000000000..175455a6c
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/consumers/ExampleConsumer.java
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms.consumers;
+
+import io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto;
+import io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto;
+import io.github.stavshamir.springwolf.example.jms.producers.AnotherProducer;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class ExampleConsumer {
+ private final AnotherProducer anotherProducer;
+
+ @JmsListener(destination = "example-queue")
+ public void receiveExamplePayload(ExamplePayloadDto payload) {
+ log.info("Received new message in example-queue: {}", payload.toString());
+
+ AnotherPayloadDto example = new AnotherPayloadDto();
+ example.setExample(payload);
+ example.setFoo("foo");
+
+ anotherProducer.sendMessage(example);
+ }
+
+ @JmsListener(destination = "another-queue")
+ public void receiveAnotherPayload(AnotherPayloadDto payload) {
+ log.info("Received new message in another-queue: {}", payload.toString());
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/dtos/AnotherPayloadDto.java b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/dtos/AnotherPayloadDto.java
new file mode 100644
index 000000000..102eed06f
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/dtos/AnotherPayloadDto.java
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms.dtos;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+@Schema(description = "Another payload model")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AnotherPayloadDto {
+
+ @Schema(description = "Foo field", example = "bar", requiredMode = NOT_REQUIRED)
+ private String foo;
+
+ @Schema(description = "Example field", requiredMode = REQUIRED)
+ private ExamplePayloadDto example;
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/dtos/ExamplePayloadDto.java b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/dtos/ExamplePayloadDto.java
new file mode 100644
index 000000000..70966ca38
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/dtos/ExamplePayloadDto.java
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms.dtos;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+@Schema(description = "Example payload model")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ExamplePayloadDto {
+ @Schema(description = "Some string field", example = "some string value", requiredMode = REQUIRED)
+ private String someString;
+
+ @Schema(description = "Some long field", example = "5")
+ private long someLong;
+
+ @Schema(description = "Some enum field", example = "FOO2", requiredMode = REQUIRED)
+ private ExampleEnum someEnum;
+
+ public enum ExampleEnum {
+ FOO1,
+ FOO2,
+ FOO3
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/producers/AnotherProducer.java b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/producers/AnotherProducer.java
new file mode 100644
index 000000000..8c5a12aa5
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/java/io/github/stavshamir/springwolf/example/jms/producers/AnotherProducer.java
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms.producers;
+
+import io.github.stavshamir.springwolf.addons.generic_binding.annotation.AsyncGenericOperationBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncPublisher;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding;
+import io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class AnotherProducer {
+ private final JmsTemplate template;
+
+ public static final String QUEUE = "another-queue";
+
+ @AsyncPublisher(
+ operation =
+ @AsyncOperation(
+ channelName = QUEUE,
+ description = "Custom, optional description defined in the AsyncPublisher annotation"))
+ @JmsAsyncOperationBinding
+ @AsyncGenericOperationBinding(
+ type = "jms",
+ fields = {"internal-field=customValue", "nested.key=nestedValue"})
+ public void sendMessage(AnotherPayloadDto msg) {
+ template.convertAndSend(QUEUE, msg);
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties b/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties
new file mode 100644
index 000000000..e4675a685
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties
@@ -0,0 +1,33 @@
+#########
+# Spring configuration
+spring.application.name=Springwolf example project - JMS
+
+
+#########
+# JMS configuration
+spring.artemis.broker-url=${BOOTSTRAP_SERVER:tcp://localhost:61616}
+spring.artemis.user=artemis
+spring.artemis.password=artemis
+
+
+#########
+# Springwolf configuration
+springwolf.enabled=true
+springwolf.docket.base-package=io.github.stavshamir.springwolf.example.jms
+springwolf.docket.info.title=${spring.application.name}
+springwolf.docket.info.version=1.0.0
+springwolf.docket.info.description=Springwolf example project to demonstrate springwolfs abilities
+springwolf.docket.info.terms-of-service=http://asyncapi.org/terms
+springwolf.docket.info.contact.name=springwolf
+springwolf.docket.info.contact.email=example@example.com
+springwolf.docket.info.contact.url=https://github.com/springwolf/springwolf-core
+springwolf.docket.info.license.name=Apache License 2.0
+springwolf.docket.servers.jms.protocol=jms
+springwolf.docket.servers.jms.url=${spring.artemis.broker-url}
+springwolf.use-fqn=true
+
+springwolf.plugin.jms.publishing.enabled=true
+
+
+# For debugging purposes
+logging.level.io.github.stavshamir.springwolf=DEBUG
diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java
new file mode 100644
index 000000000..1b9202b03
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest(
+ classes = {SpringwolfJmsExampleApplication.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ExtendWith({JmsTestContainerExtension.class})
+public class ApiIntegrationTest {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Value("${server.port}")
+ public Integer serverPort;
+
+ @Test
+ void asyncApiResourceArtifactTest() throws IOException {
+ String url = "/springwolf/docs";
+ String actual = restTemplate.getForObject(url, String.class);
+ Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actual);
+
+ InputStream s = this.getClass().getResourceAsStream("/asyncapi.json");
+ String expectedWithoutServersJmsUrlPatch =
+ new String(s.readAllBytes(), StandardCharsets.UTF_8).replace("\r\n", "\n");
+ String expected = expectedWithoutServersJmsUrlPatch.replace("activemq:61616", "localhost:61616");
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiSystemTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiSystemTest.java
new file mode 100644
index 000000000..c31b6e37d
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiSystemTest.java
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.web.client.RestTemplate;
+import org.testcontainers.containers.DockerComposeContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * While the assertion of this test is identical to ApiIntegrationTests,
+ * the setup uses a full docker-compose context with a real jms instance.
+ */
+@Testcontainers
+// @Ignore("Uncomment this line if you have issues running this test on your local machine.")
+public class ApiSystemTest {
+
+ private static final RestTemplate restTemplate = new RestTemplate();
+ private static final String APP_NAME = "app_1";
+ private static final int APP_PORT = 8080;
+
+ private static final Map ENV = new HashMap<>();
+
+ static {
+ try (InputStream input = new FileInputStream(".env")) {
+ var properties = new Properties();
+ properties.load(input);
+ properties.forEach((key, value) -> ENV.put(String.valueOf(key), String.valueOf(value)));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Container
+ public DockerComposeContainer> environment = new DockerComposeContainer<>(new File("docker-compose.yml"))
+ .withExposedService(APP_NAME, APP_PORT)
+ .withEnv(ENV);
+
+ private String baseUrl() {
+ String host = environment.getServiceHost(APP_NAME, APP_PORT);
+ int port = environment.getServicePort(APP_NAME, APP_PORT);
+ return String.format("http://%s:%d", host, port);
+ }
+
+ @Test
+ void asyncapiDocsShouldReturnTheCorrectJsonResponse() throws IOException {
+ String url = baseUrl() + "/springwolf/docs";
+ String actual = restTemplate.getForObject(url, String.class);
+
+ InputStream s = this.getClass().getResourceAsStream("/asyncapi.json");
+ String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8).replace("\r\n", "\n");
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/JmsTestContainerExtension.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/JmsTestContainerExtension.java
new file mode 100644
index 000000000..fffba6c81
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/JmsTestContainerExtension.java
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms;
+
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
+
+/**
+ * JUnit5 extension to start the localstack testcontainers once
+ * and keep it running until all test classes have been completed.
+ */
+public class JmsTestContainerExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
+
+ private static volatile boolean started = false;
+
+ static GenericContainer> activeMq =
+ new GenericContainer<>(DockerImageName.parse("apache/activemq-artemis:2.31.2")).withExposedPorts(61616);
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) {
+ if (!started) {
+ started = true;
+
+ beforeAllOnce();
+
+ // Ensure closeableResource {@see #close()} method is called
+ extensionContext.getRoot().getStore(GLOBAL).put("any unique name", this);
+ }
+ }
+
+ private static void beforeAllOnce() {
+ activeMq.start();
+ }
+
+ @Override
+ public void close() {
+ activeMq.stop();
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerSystemTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerSystemTest.java
new file mode 100644
index 000000000..42f9dfe92
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerSystemTest.java
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms;
+
+import io.github.stavshamir.springwolf.example.jms.consumers.ExampleConsumer;
+import io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto;
+import io.github.stavshamir.springwolf.producer.SpringwolfJmsProducer;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.test.annotation.DirtiesContext;
+import org.testcontainers.containers.DockerComposeContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.io.File;
+import java.util.Map;
+
+import static io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto.ExampleEnum.FOO1;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * While the assertion of this test is identical to ApiIntegrationTests,
+ * the setup uses a full docker-compose context with a real jms instance.
+ */
+@SpringBootTest(
+ classes = {SpringwolfJmsExampleApplication.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Testcontainers
+@DirtiesContext
+@TestMethodOrder(OrderAnnotation.class)
+// @Ignore("Uncomment this line if you have issues running this test on your local machine.")
+public class ProducerSystemTest {
+
+ @Autowired
+ SpringwolfJmsProducer springwolfJmsProducer;
+
+ @SpyBean
+ ExampleConsumer exampleConsumer;
+
+ @Container
+ public static DockerComposeContainer> environment = new DockerComposeContainer<>(new File("docker-compose.yml"))
+ .withServices("activemq")
+ .waitingFor("activemq", Wait.forLogMessage(".*Artemis Console available.*", 1));
+
+ @Test
+ @Order(2)
+ void producerCanUseSpringwolfConfigurationToSendMessage() {
+ // given
+ ExamplePayloadDto payload = new ExamplePayloadDto();
+ payload.setSomeString("foo");
+ payload.setSomeLong(5);
+ payload.setSomeEnum(FOO1);
+
+ // when
+ springwolfJmsProducer.send("example-queue", Map.of(), payload);
+
+ // then
+ verify(exampleConsumer, timeout(10000)).receiveExamplePayload(payload);
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/SpringwolfJmsExampleApplicationIntegrationTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/SpringwolfJmsExampleApplicationIntegrationTest.java
new file mode 100644
index 000000000..5590987fd
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/SpringwolfJmsExampleApplicationIntegrationTest.java
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.example.jms;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@SpringBootTest
+@ExtendWith({JmsTestContainerExtension.class})
+class SpringwolfJmsExampleApplicationIntegrationTest {
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Test
+ void testContext() {
+ assertNotNull(context);
+ }
+}
diff --git a/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json
new file mode 100644
index 000000000..e8469db42
--- /dev/null
+++ b/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json
@@ -0,0 +1,172 @@
+{
+ "asyncapi": "2.6.0",
+ "info": {
+ "title": "Springwolf example project - JMS",
+ "version": "1.0.0",
+ "description": "Springwolf example project to demonstrate springwolfs abilities",
+ "contact": {
+ "name": "springwolf",
+ "url": "https://github.com/springwolf/springwolf-core",
+ "email": "example@example.com"
+ },
+ "license": {
+ "name": "Apache License 2.0"
+ }
+ },
+ "defaultContentType": "application/json",
+ "servers": {
+ "jms": {
+ "url": "tcp://activemq:61616",
+ "protocol": "jms"
+ }
+ },
+ "channels": {
+ "another-queue": {
+ "subscribe": {
+ "operationId": "another-queue_subscribe",
+ "description": "Custom, optional description defined in the AsyncPublisher annotation",
+ "bindings": {
+ "jms": {
+ "internal-field": "customValue",
+ "nested": {
+ "key": "nestedValue"
+ }
+ }
+ },
+ "message": {
+ "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
+ "name": "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto",
+ "title": "AnotherPayloadDto",
+ "description": "Another payload model",
+ "payload": {
+ "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto"
+ },
+ "headers": {
+ "$ref": "#/components/schemas/HeadersNotDocumented"
+ },
+ "bindings": {
+ "jms": { }
+ }
+ }
+ },
+ "publish": {
+ "operationId": "another-queue_publish_receiveAnotherPayload",
+ "description": "Auto-generated description",
+ "bindings": {
+ "jms": { }
+ },
+ "message": {
+ "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
+ "name": "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto",
+ "title": "AnotherPayloadDto",
+ "payload": {
+ "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto"
+ },
+ "headers": {
+ "$ref": "#/components/schemas/HeadersNotDocumented"
+ },
+ "bindings": {
+ "jms": { }
+ }
+ }
+ }
+ },
+ "example-queue": {
+ "publish": {
+ "operationId": "example-queue_publish_receiveExamplePayload",
+ "description": "Auto-generated description",
+ "bindings": {
+ "jms": { }
+ },
+ "message": {
+ "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0",
+ "name": "io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto",
+ "title": "ExamplePayloadDto",
+ "payload": {
+ "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto"
+ },
+ "headers": {
+ "$ref": "#/components/schemas/HeadersNotDocumented"
+ },
+ "bindings": {
+ "jms": { }
+ }
+ }
+ },
+ "bindings": {
+ "jms": { }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HeadersNotDocumented": {
+ "type": "object",
+ "properties": { },
+ "example": { }
+ },
+ "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto": {
+ "required": [
+ "example"
+ ],
+ "type": "object",
+ "properties": {
+ "example": {
+ "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto"
+ },
+ "foo": {
+ "type": "string",
+ "description": "Foo field",
+ "example": "bar"
+ }
+ },
+ "description": "Another payload model",
+ "example": {
+ "example": {
+ "someEnum": "FOO2",
+ "someLong": 5,
+ "someString": "some string value"
+ },
+ "foo": "bar"
+ }
+ },
+ "io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto": {
+ "required": [
+ "someEnum",
+ "someString"
+ ],
+ "type": "object",
+ "properties": {
+ "someEnum": {
+ "type": "string",
+ "description": "Some enum field",
+ "example": "FOO2",
+ "enum": [
+ "FOO1",
+ "FOO2",
+ "FOO3"
+ ]
+ },
+ "someLong": {
+ "type": "integer",
+ "description": "Some long field",
+ "format": "int64",
+ "example": 5
+ },
+ "someString": {
+ "type": "string",
+ "description": "Some string field",
+ "example": "some string value"
+ }
+ },
+ "description": "Example payload model",
+ "example": {
+ "someEnum": "FOO2",
+ "someLong": 5,
+ "someString": "some string value"
+ }
+ }
+ }
+ },
+ "tags": [ ]
+}
\ No newline at end of file
diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessorTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessorTest.java
new file mode 100644
index 000000000..d030234a8
--- /dev/null
+++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessorTest.java
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor;
+
+import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AmqpAsyncOperationBinding;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AmqpMessageBindingProcessorTest {
+ private final AmqpMessageBindingProcessor processor = new AmqpMessageBindingProcessor();
+
+ @Test
+ void processTest() throws NoSuchMethodException {
+ Method method = AmqpMessageBindingProcessorTest.class.getMethod("methodWithAnnotation");
+
+ ProcessedMessageBinding binding = processor.process(method).get();
+
+ assertThat(binding.getType()).isEqualTo("amqp");
+ assertThat(binding.getBinding()).isEqualTo(new AMQPMessageBinding());
+ }
+
+ @Test
+ void processWithoutAnnotationTest() throws NoSuchMethodException {
+ Method method = AmqpMessageBindingProcessorTest.class.getMethod("methodWithoutAnnotation");
+
+ Optional binding = processor.process(method);
+
+ assertThat(binding).isNotPresent();
+ }
+
+ @AmqpAsyncOperationBinding
+ public void methodWithAnnotation() {}
+
+ public void methodWithoutAnnotation() {}
+}
diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessorTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessorTest.java
new file mode 100644
index 000000000..2b87e6160
--- /dev/null
+++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessorTest.java
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor;
+
+import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AmqpAsyncOperationBinding;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AmqpOperationBindingProcessorTest {
+ private final AmqpOperationBindingProcessor processor = new AmqpOperationBindingProcessor();
+
+ @Test
+ void mapToOperationBindingTest() throws NoSuchMethodException {
+ AmqpAsyncOperationBinding annotation = AmqpOperationBindingProcessorTest.class
+ .getMethod("methodWithAnnotation")
+ .getAnnotation(AmqpAsyncOperationBinding.class);
+
+ ProcessedOperationBinding binding = processor.mapToOperationBinding(annotation);
+
+ assertThat(binding.getType()).isEqualTo("amqp");
+ assertThat(binding.getBinding())
+ .isEqualTo(
+ new AMQPOperationBinding(0, null, List.of(), 0, 0, false, null, null, false, false, "0.2.0"));
+ }
+
+ @AmqpAsyncOperationBinding
+ public void methodWithAnnotation() {}
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/README.md b/springwolf-plugins/springwolf-jms-plugin/README.md
new file mode 100644
index 000000000..1ba369656
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/README.md
@@ -0,0 +1,42 @@
+# Springwolf JMS Plugin
+
+##### Automated documentation for Spring Boot application with JMS consumers
+
+### Table Of Contents
+
+- [About](#about)
+- [Usage](#usage)
+ - [Dependencies](#dependencies)
+ - [Configuration class](#configuration-class)
+- [Verify](#verify)
+- [Example Project](#example-project)
+
+### About
+
+This plugin generates an [AsyncAPI document](https://www.asyncapi.com/) from `@JmsListener` methods.
+
+### Usage
+
+Add the following dependencies and configuration class to enable this plugin.
+
+#### Dependencies
+
+```groovy
+dependencies {
+ // Provides the documentation API
+ implementation 'io.github.springwolf:springwolf-jms:'
+
+ // Provides the UI - optional (recommended)
+ runtimeOnly 'io.github.springwolf:springwolf-ui:'
+}
+```
+
+#### Verify
+
+If you have included the UI dependency, access it with the following url: `localhost:8080/springwolf/asyncapi-ui.html`.
+If not, try the following endpoint: `localhost:8080/springwolf/docs`.
+
+### Example Project
+
+See [springwolf-jms-example](https://github.com/springwolf/springwolf-core/tree/master/springwolf-examples/springwolf-jms-example)
+.
diff --git a/springwolf-plugins/springwolf-jms-plugin/build.gradle b/springwolf-plugins/springwolf-jms-plugin/build.gradle
new file mode 100644
index 000000000..06343fd23
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/build.gradle
@@ -0,0 +1,71 @@
+plugins {
+ id 'java-library'
+
+ id 'org.springframework.boot'
+ id 'io.spring.dependency-management'
+ id 'ca.cutterslade.analyze'
+}
+
+dependencies {
+ api project(":springwolf-core")
+
+ implementation "jakarta.jms:jakarta.jms-api"
+
+ implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}"
+ implementation "org.slf4j:slf4j-api:${slf4jApiVersion}"
+
+ runtimeOnly "org.apache.activemq:activemq-broker"
+
+ implementation "org.springframework:spring-context"
+ implementation "org.springframework:spring-core"
+ implementation "org.springframework:spring-jms"
+ implementation "org.springframework:spring-web"
+
+ implementation "org.springframework.boot:spring-boot"
+ implementation "org.springframework.boot:spring-boot-autoconfigure"
+
+ compileOnly "com.google.code.findbugs:jsr305:${jsr305Version}"
+ permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}"
+
+ annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
+ annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
+
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
+ testRuntimeOnly "org.springframework.boot:spring-boot-starter-web"
+
+ testImplementation "org.assertj:assertj-core:${assertjCoreVersion}"
+
+ testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
+
+ testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}"
+
+ testImplementation "org.springframework:spring-beans"
+ testImplementation "org.springframework:spring-test"
+
+ testImplementation "org.springframework.boot:spring-boot-test"
+ testImplementation "org.springframework.boot:spring-boot-test-autoconfigure"
+
+ testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"
+}
+
+jar {
+ enabled = true
+ archiveClassifier = ''
+}
+bootJar.enabled = false
+
+java {
+ withJavadocJar()
+ withSourcesJar()
+}
+
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ pom {
+ name = 'springwolf-jms'
+ description = 'Automated JSON API documentation for JMS Listeners built with Spring'
+ }
+ }
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java
new file mode 100644
index 000000000..be56a8d2d
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.controller;
+
+import io.github.stavshamir.springwolf.asyncapi.controller.dtos.MessageDto;
+import io.github.stavshamir.springwolf.producer.SpringwolfJmsProducer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/springwolf/jms")
+@Slf4j
+public class SpringwolfJmsController extends PublishingBaseController {
+
+ private final SpringwolfJmsProducer producer;
+
+ public SpringwolfJmsController(PublishingPayloadCreator publishingPayloadCreator, SpringwolfJmsProducer producer) {
+ super(publishingPayloadCreator);
+ this.producer = producer;
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return producer.isEnabled();
+ }
+
+ @Override
+ protected void publishMessage(String topic, MessageDto message, Object payload) {
+ log.debug("Publishing to JMS queue {}: {}", topic, message);
+ producer.send(topic, message.getHeaders(), payload);
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsAutoConfiguration.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsAutoConfiguration.java
new file mode 100644
index 000000000..2c1733b6c
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsAutoConfiguration.java
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.jms;
+
+import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigConstants;
+import io.github.stavshamir.springwolf.configuration.properties.SpringwolfJmsConfigProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Autoconfiguration for the springwolf jms plugin.
+ */
+@AutoConfiguration
+@Import({SpringwolfJmsScannerConfiguration.class, SpringwolfJmsProducerConfiguration.class})
+@ConditionalOnProperty(name = SpringwolfConfigConstants.SPRINGWOLF_ENABLED, havingValue = "true", matchIfMissing = true)
+public class SpringwolfJmsAutoConfiguration {
+
+ @Bean
+ public SpringwolfJmsConfigProperties springwolfJmsConfigProperties() {
+ return new SpringwolfJmsConfigProperties();
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsProducerConfiguration.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsProducerConfiguration.java
new file mode 100644
index 000000000..87646bc79
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsProducerConfiguration.java
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.jms;
+
+import io.github.stavshamir.springwolf.asyncapi.controller.PublishingPayloadCreator;
+import io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsController;
+import io.github.stavshamir.springwolf.producer.SpringwolfJmsProducer;
+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.jms.core.JmsTemplate;
+
+import java.util.List;
+
+import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfJmsConfigConstants.SPRINGWOLF_JMS_CONFIG_PREFIX;
+import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfJmsConfigConstants.SPRINGWOLF_JMS_PLUGIN_PUBLISHING_ENABLED;
+
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnProperty(
+ prefix = SPRINGWOLF_JMS_CONFIG_PREFIX,
+ name = SPRINGWOLF_JMS_PLUGIN_PUBLISHING_ENABLED,
+ havingValue = "true")
+public class SpringwolfJmsProducerConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SpringwolfJmsProducer springwolfJmsProducer(List jmsTemplates) {
+ return new SpringwolfJmsProducer(jmsTemplates);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SpringwolfJmsController springwolfJmsController(
+ PublishingPayloadCreator publishingPayloadCreator, SpringwolfJmsProducer springwolfJmsProducer) {
+ return new SpringwolfJmsController(publishingPayloadCreator, springwolfJmsProducer);
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java
new file mode 100644
index 000000000..1767b15c8
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.jms;
+
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingProcessorPriority;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.JmsMessageBindingProcessor;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.JmsOperationBindingProcessor;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelPriority;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelJmsListenerScanner;
+import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ComponentClassScanner;
+import io.github.stavshamir.springwolf.schemas.SchemasService;
+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.core.annotation.Order;
+
+import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfJmsConfigConstants.SPRINGWOLF_SCANNER_JMS_LISTENER_ENABLED;
+
+/**
+ * spring configuration defining the scanner beans for the jms plugin
+ */
+@Configuration(proxyBeanMethods = false)
+public class SpringwolfJmsScannerConfiguration {
+
+ @Bean
+ @Order(value = ChannelPriority.AUTO_DISCOVERED)
+ @ConditionalOnProperty(name = SPRINGWOLF_SCANNER_JMS_LISTENER_ENABLED, havingValue = "true", matchIfMissing = true)
+ public MethodLevelJmsListenerScanner methodLevelJmsListenerScanner(
+ ComponentClassScanner componentClassScanner, SchemasService schemasService) {
+ return new MethodLevelJmsListenerScanner(componentClassScanner, schemasService);
+ }
+
+ @Bean
+ @Order(value = BindingProcessorPriority.PROTOCOL_BINDING)
+ @ConditionalOnMissingBean
+ public JmsMessageBindingProcessor jmsMessageBindingProcessor() {
+ return new JmsMessageBindingProcessor();
+ }
+
+ @Bean
+ @Order(value = BindingProcessorPriority.PROTOCOL_BINDING)
+ @ConditionalOnMissingBean
+ public JmsOperationBindingProcessor jmsOperationBindingProcessor() {
+ return new JmsOperationBindingProcessor();
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java
new file mode 100644
index 000000000..7d8d4db85
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor;
+
+import com.asyncapi.v2.binding.message.jms.JMSMessageBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding;
+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 JmsMessageBindingProcessor implements MessageBindingProcessor, EmbeddedValueResolverAware {
+ private StringValueResolver resolver;
+
+ @Override
+ public void setEmbeddedValueResolver(StringValueResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ @Override
+ public Optional process(Method method) {
+ return Arrays.stream(method.getAnnotations())
+ .filter(annotation -> annotation instanceof JmsAsyncOperationBinding)
+ .map(annotation -> (JmsAsyncOperationBinding) annotation)
+ .findAny()
+ .map(this::mapToMessageBinding);
+ }
+
+ private ProcessedMessageBinding mapToMessageBinding(JmsAsyncOperationBinding bindingAnnotation) {
+ JMSMessageBinding jmsMessageBinding = new JMSMessageBinding();
+
+ return new ProcessedMessageBinding(bindingAnnotation.type(), jmsMessageBinding);
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java
new file mode 100644
index 000000000..9430829a5
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor;
+
+import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding;
+
+public class JmsOperationBindingProcessor extends AbstractOperationBindingProcessor {
+
+ @Override
+ protected ProcessedOperationBinding mapToOperationBinding(JmsAsyncOperationBinding bindingAnnotation) {
+ return new ProcessedOperationBinding(bindingAnnotation.type(), new JMSOperationBinding());
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java
new file mode 100644
index 000000000..057296e97
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation;
+
+import com.asyncapi.v2.binding.channel.ChannelBinding;
+import com.asyncapi.v2.binding.channel.jms.JMSChannelBinding;
+import com.asyncapi.v2.binding.message.MessageBinding;
+import com.asyncapi.v2.binding.message.jms.JMSMessageBinding;
+import com.asyncapi.v2.binding.operation.OperationBinding;
+import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.util.StringValueResolver;
+
+import java.util.Map;
+
+@Slf4j
+public class JmsListenerUtil {
+
+ public static String getChannelName(JmsListener annotation, StringValueResolver resolver) {
+ return resolver.resolveStringValue(annotation.destination());
+ }
+
+ public static Map buildChannelBinding(
+ JmsListener annotation, StringValueResolver resolver) {
+ return Map.of("jms", new JMSChannelBinding());
+ }
+
+ public static Map buildOperationBinding(
+ JmsListener annotation, StringValueResolver resolver) {
+ return Map.of("jms", new JMSOperationBinding());
+ }
+
+ public static Map buildMessageBinding() {
+ return Map.of("jms", new JMSMessageBinding());
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelJmsListenerScanner.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelJmsListenerScanner.java
new file mode 100644
index 000000000..1df17d1bb
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelJmsListenerScanner.java
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation;
+
+import com.asyncapi.v2.binding.channel.ChannelBinding;
+import com.asyncapi.v2.binding.message.MessageBinding;
+import com.asyncapi.v2.binding.operation.OperationBinding;
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner;
+import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ComponentClassScanner;
+import io.github.stavshamir.springwolf.schemas.SchemasService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.EmbeddedValueResolverAware;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.util.StringValueResolver;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+@Slf4j
+public class MethodLevelJmsListenerScanner extends AbstractMethodLevelListenerScanner
+ implements ChannelsScanner, EmbeddedValueResolverAware {
+
+ private StringValueResolver resolver;
+
+ public MethodLevelJmsListenerScanner(ComponentClassScanner componentClassScanner, SchemasService schemasService) {
+ super(componentClassScanner, schemasService);
+ }
+
+ @Override
+ public void setEmbeddedValueResolver(StringValueResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ @Override
+ protected Class getListenerAnnotationClass() {
+ return JmsListener.class;
+ }
+
+ @Override
+ protected String getChannelName(JmsListener annotation) {
+ return JmsListenerUtil.getChannelName(annotation, resolver);
+ }
+
+ @Override
+ protected Map buildChannelBinding(JmsListener annotation) {
+ return JmsListenerUtil.buildChannelBinding(annotation, resolver);
+ }
+
+ @Override
+ protected Map buildOperationBinding(JmsListener annotation) {
+ return JmsListenerUtil.buildOperationBinding(annotation, resolver);
+ }
+
+ @Override
+ protected Map buildMessageBinding(JmsListener annotation) {
+ return JmsListenerUtil.buildMessageBinding();
+ }
+
+ protected Class> getPayloadType(Method method) {
+ return SpringPayloadAnnotationTypeExtractor.getPayloadType(method);
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/JmsAsyncOperationBinding.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/JmsAsyncOperationBinding.java
new file mode 100644
index 000000000..27190c4f4
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/JmsAsyncOperationBinding.java
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation;
+
+import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * {@code @JmsAsyncOperationBinding} is a method-level annotation used in combination with {@link AsyncPublisher} or {@link AsyncListener}.
+ * It configures the operation binding for the JMS protocol.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD})
+@AsyncOperationBinding
+public @interface JmsAsyncOperationBinding {
+
+ String type() default "jms";
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfJmsConfigConstants.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfJmsConfigConstants.java
new file mode 100644
index 000000000..1dae7aa5c
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfJmsConfigConstants.java
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.configuration.properties;
+
+import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigConstants.ENABLED;
+import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigConstants.SCANNER;
+import static io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigConstants.SPRINGWOLF_PLUGIN_CONFIG_PREFIX;
+
+public class SpringwolfJmsConfigConstants {
+
+ public static final String SPRINGWOLF_JMS_CONFIG_PREFIX = SPRINGWOLF_PLUGIN_CONFIG_PREFIX + ".jms";
+
+ public static final String SPRINGWOLF_JMS_PLUGIN_PUBLISHING_ENABLED = "publishing.enabled";
+
+ public static final String SPRINGWOLF_SCANNER_JMS_LISTENER_ENABLED =
+ SPRINGWOLF_JMS_CONFIG_PREFIX + SCANNER + ".jms-listener" + ENABLED;
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfJmsConfigProperties.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfJmsConfigProperties.java
new file mode 100644
index 000000000..861d4bd2a
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfJmsConfigProperties.java
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.configuration.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.lang.Nullable;
+
+/**
+ * This class is used to create metadata for auto-completion in spring configuration properties/yaml by using
+ * the spring-boot-configuration-processor.
+ */
+@ConfigurationProperties(prefix = SpringwolfJmsConfigConstants.SPRINGWOLF_JMS_CONFIG_PREFIX)
+@Getter
+@Setter
+public class SpringwolfJmsConfigProperties {
+
+ @Nullable
+ private Publishing publishing;
+
+ @Nullable
+ private Scanner scanner;
+
+ @Getter
+ @Setter
+ public static class Publishing {
+
+ /**
+ * Enables/Disables the possibility to publish messages through springwolf on the configured jms instance.
+ */
+ private boolean enabled = false;
+ }
+
+ @Getter
+ @Setter
+ public static class Scanner {
+
+ private static JmsListener jmsListener;
+
+ @Getter
+ @Setter
+ public static class JmsListener {
+
+ /**
+ * This mirrors the ConfigConstant {@see SpringwolfJmsConfigConstants#SPRINGWOLF_SCANNER_JMS_LISTENER_ENABLED}
+ */
+ private boolean enabled = true;
+ }
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java
new file mode 100644
index 000000000..41acc5c92
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.producer;
+
+import jakarta.jms.JMSException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jms.core.JmsTemplate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+public class SpringwolfJmsProducer {
+
+ private final Optional template;
+
+ public SpringwolfJmsProducer(List templates) {
+ this.template = templates.isEmpty() ? Optional.empty() : Optional.of(templates.get(0));
+ }
+
+ public boolean isEnabled() {
+ return template.isPresent();
+ }
+
+ public void send(String channelName, Map headers, Object payload) {
+ if (template.isPresent()) {
+ template.get().convertAndSend(channelName, payload, message -> {
+ if (headers != null) {
+ headers.forEach((name, value) -> {
+ try {
+ message.setStringProperty(name, value);
+ } catch (JMSException ex) {
+ log.warn("Unable to set JMS Header key=%s value=%s".formatted(name, value), ex);
+ }
+ });
+ }
+ return message;
+ });
+
+ } else {
+ log.warn("JMS producer is not configured");
+ }
+ }
+}
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springwolf-plugins/springwolf-jms-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..c380b3330
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+io.github.stavshamir.springwolf.asyncapi.jms.SpringwolfJmsAutoConfiguration
diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java
new file mode 100644
index 000000000..f72a1208a
--- /dev/null
+++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: Apache-2.0
+package io.github.stavshamir.springwolf.asyncapi.controller;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties;
+import io.github.stavshamir.springwolf.producer.SpringwolfJmsProducer;
+import io.github.stavshamir.springwolf.schemas.DefaultSchemasService;
+import io.github.stavshamir.springwolf.schemas.SchemasService;
+import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.jackson.Jacksonized;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.Map;
+
+import static java.util.Collections.singletonMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(SpringwolfJmsController.class)
+@ContextConfiguration(
+ classes = {
+ SpringwolfJmsController.class,
+ PublishingPayloadCreator.class,
+ SpringwolfJmsProducer.class,
+ DefaultSchemasService.class,
+ ExampleJsonGenerator.class,
+ SpringwolfConfigProperties.class,
+ })
+@TestPropertySource(
+ properties = {
+ "springwolf.docket.base-package=io.github.stavshamir.springwolf.asyncapi",
+ "springwolf.docket.info.title=Title",
+ "springwolf.docket.info.version=1.0",
+ "springwolf.docket.servers.jms.protocol=jms",
+ "springwolf.docket.servers.jms.url=127.0.0.1",
+ "springwolf.plugin.jms.publishing.enabled=true",
+ "springwolf.use-fqn=true"
+ })
+class SpringwolfJmsControllerIntegrationTest {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private SchemasService schemasService;
+
+ @MockBean
+ private SpringwolfJmsProducer springwolfJmsProducer;
+
+ @Captor
+ private ArgumentCaptor payloadCaptor;
+
+ @Captor
+ private ArgumentCaptor