diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 407bece5..b146e555 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,14 +40,13 @@ jobs: distribution: temurin java-version: 17 - name: Execute build test jacocoTestReport and sonar analysis - if: endsWith(github.REF, '/master') == true + if: endsWith(github.REF, '/master') == true || github.event.pull_request.head.repo.fork == false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build test jacocoTestReport sonar --refresh-dependencies --no-daemon --continue -Denv.ci=true + run: ./gradlew clean build generateMergedReport sonar --refresh-dependencies --no-daemon --continue -Denv.ci=true - name: Execute build test jacocoTestReport pull request - if: endsWith(github.REF, '/merge') == true + if: github.event.pull_request.head.repo.fork == true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build test jacocoTestReport --refresh-dependencies --no-daemon --continue -Denv.ci=true \ No newline at end of file + run: ./gradlew clean build generateMergedReport --refresh-dependencies --no-daemon --continue -Denv.ci=true \ No newline at end of file diff --git a/async/async-commons/async-commons.gradle b/async/async-commons/async-commons.gradle index 12176cf5..58d5e99b 100644 --- a/async/async-commons/async-commons.gradle +++ b/async/async-commons/async-commons.gradle @@ -10,8 +10,8 @@ dependencies { compileOnly 'io.projectreactor:reactor-core' api 'com.fasterxml.jackson.core:jackson-databind' api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' - implementation 'commons-io:commons-io:2.16.1' + implementation 'commons-io:commons-io:2.17.0' implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' testImplementation 'io.projectreactor:reactor-test' -} +} \ No newline at end of file diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java index fa588ad0..e3bd99f9 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java @@ -1,8 +1,11 @@ package org.reactivecommons.async.commons.config; +import lombok.Getter; + import java.time.Duration; import java.util.UUID; +@Getter public class BrokerConfig { private final String routingKey = UUID.randomUUID().toString().replaceAll("-", ""); private final boolean persistentQueries; @@ -24,24 +27,4 @@ public BrokerConfig(boolean persistentQueries, boolean persistentCommands, boole this.replyTimeout = replyTimeout; } - public boolean isPersistentQueries() { - return persistentQueries; - } - - public boolean isPersistentCommands() { - return persistentCommands; - } - - public boolean isPersistentEvents() { - return persistentEvents; - } - - public Duration getReplyTimeout() { - return replyTimeout; - } - - public String getRoutingKey() { - return routingKey; - } - } diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java new file mode 100644 index 00000000..989f30e7 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java @@ -0,0 +1,78 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; +import reactor.core.publisher.Mono; + +public class KafkaDirectAsyncGateway implements DirectAsyncGateway { + + public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; + + @Override + public Mono sendCommand(Command command, String targetName) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(Command command, String targetName, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono reply(T response, From from) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java index d24f0a76..cbd5a09b 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java @@ -9,6 +9,7 @@ @AllArgsConstructor public class KafkaDomainEventBus implements DomainEventBus { + public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; private final ReactiveMessageSender sender; @Override @@ -16,8 +17,18 @@ public Publisher emit(DomainEvent event) { return sender.send(event); } + @Override + public Publisher emit(String domain, DomainEvent event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + @Override public Publisher emit(CloudEvent event) { return sender.send(event); } + + @Override + public Publisher emit(String domain, CloudEvent event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } } diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java new file mode 100644 index 00000000..d1229f58 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java @@ -0,0 +1,48 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class KafkaDirectAsyncGatewayTest { + private final DirectAsyncGateway directAsyncGateway = new KafkaDirectAsyncGateway(); + private final String targetName = "targetName"; + private final String domain = "domain"; + private final long delay = 1000L; + @Mock + private CloudEvent cloudEvent; + @Mock + private Command command; + @Mock + private AsyncQuery query; + @Mock + private From from; + + @Test + void allMethodsAreNotImplemented() { + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(cloudEvent, targetName)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(cloudEvent, targetName, domain)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(cloudEvent, targetName, delay)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(cloudEvent, targetName, delay, domain)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(command, targetName)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(command, targetName, domain)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(command, targetName, delay)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.sendCommand(command, targetName, delay, domain)); + + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.requestReply(cloudEvent, targetName, CloudEvent.class)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.requestReply(cloudEvent, targetName, CloudEvent.class, domain)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.requestReply(query, targetName, CloudEvent.class)); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.requestReply(query, targetName, CloudEvent.class, domain)); + + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.reply(targetName, from)); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java new file mode 100644 index 00000000..0dc04cdf --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java @@ -0,0 +1,56 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KafkaDomainEventBusTest { + @Mock + private DomainEvent domainEvent; + @Mock + private CloudEvent cloudEvent; + @Mock + private ReactiveMessageSender sender; + @InjectMocks + private KafkaDomainEventBus kafkaDomainEventBus; + private final String domain = "domain"; + + @Test + void shouldEmitDomainEvent() { + // Arrange + when(sender.send(domainEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(kafkaDomainEventBus.emit(domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldEmitCloudEvent() { + // Arrange + when(sender.send(cloudEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(kafkaDomainEventBus.emit(cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void operationsShouldNotBeAbleForDomains() { + assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, domainEvent)); + assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, cloudEvent)); + } +} diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java index bb249453..d930f22c 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java @@ -1,12 +1,12 @@ package org.reactivecommons.async.rabbit; import io.cloudevents.CloudEvent; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.api.domain.DomainEventBus; import java.util.Collections; @@ -29,7 +29,12 @@ public RabbitDomainEventBus(ReactiveMessageSender sender, String exchange, Broke @Override public Mono emit(DomainEvent event) { return sender.sendWithConfirm(event, exchange, event.getName(), Collections.emptyMap(), persistentEvents) - .onErrorMap(err -> new RuntimeException("Event send failure: " + event.getName(), err)); + .onErrorMap(err -> new RuntimeException("Event send failure: " + event.getName(), err)); + } + + @Override + public Publisher emit(String domain, DomainEvent event) { + throw new UnsupportedOperationException("Not implemented yet"); } @Override @@ -39,4 +44,9 @@ public Publisher emit(CloudEvent cloudEvent) { .onErrorMap(err -> new RuntimeException("Event send failure: " + cloudEvent.getType(), err)); } + @Override + public Publisher emit(String domain, CloudEvent event) { + throw new UnsupportedOperationException("Not implemented yet"); + } + } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java index c5e204b5..6a6e4336 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java @@ -7,10 +7,10 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.EventExecutor; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Flux; @@ -51,9 +51,6 @@ public ApplicationNotificationListener(ReactiveMessageListener receiver, } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(exchange(exchangeName) - .type("topic") - .durable(true)); final Mono declareQueue = creator.declare( queue(queueName) @@ -65,6 +62,10 @@ protected Mono setUpBindings(TopologyCreator creator) { .flatMap(listener -> creator.bind(binding(exchangeName, listener.getPath(), queueName))); if (createTopology) { + final Mono declareExchange = creator.declare(exchange(exchangeName) + .type("topic") + .durable(true)); + return declareExchange .then(declareQueue) .thenMany(bindings) diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java index a6555431..27811aa5 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java @@ -139,7 +139,8 @@ protected Mono handle(AcknowledgableDelivery msj, Instan } private void onTerminate() { - messageFlux.doOnTerminate(this::onTerminate) + messageFlux + .doOnTerminate(this::onTerminate) .subscribe(new LoggerSubscriber<>(getClass().getName())); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java new file mode 100644 index 00000000..38ed6e4c --- /dev/null +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java @@ -0,0 +1,67 @@ +package org.reactivecommons.async.rabbit; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RabbitDomainEventBusTest { + @Mock + private DomainEvent domainEvent; + @Mock + private CloudEvent cloudEvent; + @Mock + private ReactiveMessageSender sender; + private RabbitDomainEventBus rabbitDomainEventBus; + private final String domain = "domain"; + + @BeforeEach + void setUp() { + rabbitDomainEventBus = new RabbitDomainEventBus(sender, "exchange"); + } + + @Test + void shouldEmitDomainEvent() { + // Arrange + when(domainEvent.getName()).thenReturn("event"); + when(sender.sendWithConfirm(any(DomainEvent.class), anyString(), anyString(), any(), anyBoolean())) + .thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(rabbitDomainEventBus.emit(domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldEmitCloudEvent() { + // Arrange + when(cloudEvent.getType()).thenReturn("event"); + when(sender.sendWithConfirm(any(CloudEvent.class), anyString(), anyString(), any(), anyBoolean())) + .thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(rabbitDomainEventBus.emit(cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void operationsShouldNotBeAbleForDomains() { + assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, domainEvent)); + assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, cloudEvent)); + } +} diff --git a/build.gradle b/build.gradle index 251626ad..67426f4e 100644 --- a/build.gradle +++ b/build.gradle @@ -13,18 +13,9 @@ buildscript { plugins { id 'jacoco' id 'org.sonarqube' version '5.1.0.4882' - id 'org.springframework.boot' version '3.3.1' apply false + id 'org.springframework.boot' version '3.3.4' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.17.13' -} - -sonar { - properties { - property 'sonar.projectKey', 'reactive-commons_reactive-commons-java' - property 'sonar.coverage.exclusions', 'samples/**/*' - property 'sonar.organization', 'reactive-commons' - property 'sonar.host.url', 'https://sonarcloud.io' - } + id 'co.com.bancolombia.cleanArchitecture' version '3.17.26' } repositories { diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index 3deb07a1..36fa4c10 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -83,7 +83,7 @@ spring: You can also set it in runtime for example from a secret, so you can create the `RabbitProperties` bean like: -```java title="org.reactivecommons.async.rabbit.config.RabbitProperties" +```java title="org.reactivecommons.async.rabbit.standalone.config.RabbitProperties" @Configuration public class MyRabbitMQConfig { diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index 2155dacb..c64b9ad4 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -27,6 +27,9 @@ app: createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. delayedCommands: false # Enable to send a delayed command to an external target prefetchCount: 250 # is the maximum number of in flight messages you can reduce it to process less concurrent messages, this settings acts per instance of your service + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "rabbitmq" # please don't change this value flux: maxConcurrency: 250 # max concurrency of listener flow domain: @@ -64,7 +67,7 @@ You can override this settings programmatically through a `AsyncPropsDomainPrope ```java package sample; -import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.standalone.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.props.AsyncProps; import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; import org.springframework.context.annotation.Bean; @@ -133,6 +136,9 @@ reactive: retryDelay: 1000 # interval for message retries, with and without DLQRetry checkExistingTopics: true # if you don't want to verify topic existence before send a record you can set it to false createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "kafka" # please don't change this value domain: ignoreThisListener: false # Allows you to disable event listener for this specific domain connectionProperties: # you can override the connection properties of each domain diff --git a/docs/package-lock.json b/docs/package-lock.json index 97b8f37e..82daf899 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -4257,9 +4257,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4269,7 +4269,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4948,9 +4948,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -5797,9 +5797,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -6102,36 +6102,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6167,9 +6167,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -6354,12 +6354,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8615,9 +8615,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -10558,9 +10561,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10888,9 +10894,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dependencies": { "isarray": "0.0.1" } @@ -11696,11 +11702,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -12719,9 +12725,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -12754,6 +12760,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -12866,14 +12880,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java index df697403..458a0c0c 100644 --- a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java @@ -5,6 +5,8 @@ public interface DomainEventBus { Publisher emit(DomainEvent event); + Publisher emit(String domain, DomainEvent event); Publisher emit(CloudEvent event); + Publisher emit(String domain, CloudEvent event); } diff --git a/gradle.properties b/gradle.properties index 80e3741c..d6a490b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.1-beta -toPublish=domain-events-api,async-commons-api,async-commons,shared-starter,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter +version=5.0.1-betaLOCAL +toPublish=domain-events-api,async-commons-api,async-commons,shared-starter,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter,async-commons-starter onlyUpdater=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..a4b76b95 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3499ded5..df97d72b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,9 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +134,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/main.gradle b/main.gradle index f06cfc60..64978624 100644 --- a/main.gradle +++ b/main.gradle @@ -11,6 +11,26 @@ allprojects { maven { url 'https://repo.spring.io/milestone' } } + if (toPublish.split(',').contains(project.name) || project.name == rootProject.name) { + + sonar { + properties { + property "sonar.sourceEncoding", "UTF-8" + property 'sonar.projectKey', 'reactive-commons_reactive-commons-java' + property 'sonar.organization', 'reactive-commons' + property 'sonar.host.url', 'https://sonarcloud.io' + property "sonar.sources", "src/main" + property "sonar.test", "src/test" + property "sonar.java.binaries", "build/classes" + property "sonar.junit.reportPaths", "build/test-results/test" + property "sonar.java-coveragePlugin", "jacoco" + property "sonar.coverage.jacoco.xmlReportPaths", "${rootDir}/build/reports/jacoco/generateMergedReport/generateMergedReport.xml" + property "sonar.exclusions", ".github/**,samples/**/*,**/mybroker/**/*,**/standalone/**/*" + property 'sonar.coverage.exclusions', 'samples/**/*' + } + } + } + group 'org.reactivecommons' } @@ -39,9 +59,14 @@ subprojects { testCompileOnly 'org.projectlombok:lombok' } + test.finalizedBy(project.tasks.jacocoTestReport) + jacocoTestReport { + dependsOn test reports { xml.setRequired true + html.setRequired true + csv.setRequired false } } @@ -55,7 +80,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.3.1' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.3.4' } } @@ -134,6 +159,22 @@ subprojects { } } +tasks.register('generateMergedReport', JacocoReport) { + dependsOn test + dependsOn subprojects.test + dependsOn subprojects.javadoc + dependsOn subprojects.jacocoTestReport + additionalSourceDirs.setFrom files(subprojects.sourceSets.main.allSource.srcDirs) + sourceDirectories.setFrom files(subprojects.sourceSets.main.allSource.srcDirs) + classDirectories.setFrom files(subprojects.sourceSets.main.output).filter({ !it.toString().contains("sample") }) + executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/test.exec') + reports { + xml.setRequired true + csv.setRequired false + html.setRequired true + } +} + tasks.named('wrapper') { - gradleVersion = '8.8' + gradleVersion = '8.10.2' } \ No newline at end of file diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java b/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java index f2c87a97..eaf287da 100644 --- a/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java @@ -1,7 +1,7 @@ package sample; import lombok.extern.java.Log; -import org.reactivecommons.async.kafka.annotations.EnableDomainEventBus; +import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java b/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java index 85e0ad3a..f1eabbba 100644 --- a/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java @@ -1,6 +1,6 @@ package sample; -import org.reactivecommons.async.kafka.config.RCKafkaConfig; +import org.reactivecommons.async.kafka.KafkaSetupUtils; import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,7 +20,7 @@ public AsyncKafkaProps kafkaProps() throws IOException { kafkaProps.setMaxRetries(5); kafkaProps.setRetryDelay(1000); kafkaProps.setWithDLQRetry(true); - kafkaProps.setConnectionProperties(RCKafkaConfig.readPropsFromDotEnv(Path.of(".kafka-env"))); + kafkaProps.setConnectionProperties(KafkaSetupUtils.readPropsFromDotEnv(Path.of(".kafka-env"))); return kafkaProps; } } diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java b/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java index ababe387..a9b4c9f1 100644 --- a/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java @@ -4,7 +4,7 @@ import lombok.extern.log4j.Log4j2; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.kafka.annotations.EnableEventListeners; +import org.reactivecommons.async.impl.config.annotations.EnableEventListeners; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; diff --git a/samples/async/no-springboot-client/no-springboot-client.gradle b/samples/async/no-springboot-client/no-springboot-client.gradle deleted file mode 100644 index 5685503e..00000000 --- a/samples/async/no-springboot-client/no-springboot-client.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - implementation project(':async-commons-rabbit-starter') -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index b64577d1..410651d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,7 +13,7 @@ FileTree buildFiles = fileTree(rootDir) { String rootDirPath = rootDir.absolutePath + File.separator buildFiles.each { File buildFile -> - boolean isDefaultName = 'build.gradle'.equals(buildFile.name) + boolean isDefaultName = 'build.gradle' == buildFile.name if (isDefaultName) { String buildFilePath = buildFile.parentFile.absolutePath String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':') diff --git a/starters/async-commons-starter/async-commons-starter.gradle b/starters/async-commons-starter/async-commons-starter.gradle new file mode 100644 index 00000000..43493bd0 --- /dev/null +++ b/starters/async-commons-starter/async-commons-starter.gradle @@ -0,0 +1,17 @@ +ext { + artifactId = 'async-commons-starter' + artifactDescription = 'Async Commons Starter for Spring Boot' +} +dependencies { + api 'io.projectreactor:reactor-core' + api project(':async-commons') + compileOnly 'org.springframework.boot:spring-boot-starter' + compileOnly 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + + testImplementation 'io.projectreactor:reactor-test' + testImplementation 'org.springframework.boot:spring-boot-starter-actuator' +} \ No newline at end of file diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java similarity index 50% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java index fcbe9833..e3181e2e 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java @@ -1,16 +1,20 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; +import org.reactivecommons.async.starter.listeners.CommandsListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import(CommandListenersConfig.class) +@Import(CommandsListenerConfig.class) @Configuration public @interface EnableCommandListeners { } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java similarity index 56% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java index 359913fd..300297dd 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java @@ -1,10 +1,14 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.DirectAsyncGatewayConfig; +import org.reactivecommons.async.starter.senders.DirectAsyncGatewayConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java similarity index 74% rename from starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java index f95879f0..b3e33c09 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java @@ -1,6 +1,6 @@ -package org.reactivecommons.async.kafka.annotations; +package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.kafka.config.RCKafkaConfig; +import org.reactivecommons.async.starter.senders.EventBusConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -14,7 +14,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import(RCKafkaConfig.class) +@Import(EventBusConfig.class) @Configuration public @interface EnableDomainEventBus { } diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java similarity index 55% rename from starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java index 366c97e0..c5f9839d 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java @@ -1,8 +1,6 @@ -package org.reactivecommons.async.kafka.annotations; +package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.kafka.config.RCKafkaConfig; -import org.reactivecommons.async.kafka.config.RCKafkaEventListenerConfig; -import org.reactivecommons.async.kafka.config.RCKafkaHandlersConfiguration; +import org.reactivecommons.async.starter.listeners.EventsListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -15,7 +13,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import({RCKafkaEventListenerConfig.class, RCKafkaHandlersConfiguration.class, RCKafkaConfig.class}) +@Import(EventsListenerConfig.class) @Configuration public @interface EnableEventListeners { } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java new file mode 100644 index 00000000..3107c83e --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java @@ -0,0 +1,30 @@ +package org.reactivecommons.async.impl.config.annotations; + +import org.reactivecommons.async.starter.listeners.CommandsListenerConfig; +import org.reactivecommons.async.starter.listeners.EventsListenerConfig; +import org.reactivecommons.async.starter.listeners.NotificationEventsListenerConfig; +import org.reactivecommons.async.starter.listeners.QueriesListenerConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation enables all messages listeners (Query, Commands, Events). If you want to enable separately, please use + * EnableCommandListeners, EnableQueryListeners or EnableEventListeners. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import({CommandsListenerConfig.class, QueriesListenerConfig.class, EventsListenerConfig.class, + NotificationEventsListenerConfig.class}) +@Configuration +public @interface EnableMessageListeners { +} + + + diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java similarity index 54% rename from starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java index 1ae0af73..e4ce2e36 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java @@ -1,8 +1,6 @@ -package org.reactivecommons.async.kafka.annotations; +package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.kafka.config.RCKafkaConfig; -import org.reactivecommons.async.kafka.config.RCKafkaHandlersConfiguration; -import org.reactivecommons.async.kafka.config.RCKafkaNotificationEventListenerConfig; +import org.reactivecommons.async.starter.listeners.NotificationEventsListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -16,7 +14,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import({RCKafkaNotificationEventListenerConfig.class, RCKafkaHandlersConfiguration.class, RCKafkaConfig.class}) +@Import(NotificationEventsListenerConfig.class) @Configuration public @interface EnableNotificationListener { } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java similarity index 50% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java index 6eb878b0..001433e2 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java @@ -1,16 +1,20 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; +import org.reactivecommons.async.starter.listeners.QueriesListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import(QueryListenerConfig.class) +@Import(QueriesListenerConfig.class) @Configuration public @interface EnableQueryListeners { } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java new file mode 100644 index 00000000..88d438d4 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.broker; + +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.props.GenericAsyncProps; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; + +@SuppressWarnings("rawtypes") +public interface BrokerProvider { + T getProps(); + + DomainEventBus getDomainBus(); + + DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver); + + void listenDomainEvents(HandlerResolver resolver); + + void listenNotificationEvents(HandlerResolver resolver); + + void listenCommands(HandlerResolver resolver); + + void listenQueries(HandlerResolver resolver); + + void listenReplies(HandlerResolver resolver); + + Mono healthCheck(); +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java new file mode 100644 index 00000000..8a599069 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java @@ -0,0 +1,13 @@ +package org.reactivecommons.async.starter.broker; + +import org.reactivecommons.async.starter.props.GenericAsyncProps; + +@SuppressWarnings("rawtypes") +public interface BrokerProviderFactory { + String getBrokerType(); + + DiscardProvider getDiscardProvider(T props); + + BrokerProvider getProvider(String domain, T props, DiscardProvider discardProvider); + +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java new file mode 100644 index 00000000..aa735233 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java @@ -0,0 +1,8 @@ +package org.reactivecommons.async.starter.broker; + +import org.reactivecommons.async.commons.DiscardNotifier; + +import java.util.function.Supplier; + +public interface DiscardProvider extends Supplier { +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java new file mode 100644 index 00000000..92ba2630 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.starter.config; + +import org.reactivecommons.async.starter.broker.BrokerProvider; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiConsumer; + +@SuppressWarnings("rawtypes") +public class ConnectionManager { + private final Map connections = new TreeMap<>(); + + public void forDomain(BiConsumer consumer) { + connections.forEach(consumer); + } + + public ConnectionManager addDomain(String domain, BrokerProvider domainConn) { + connections.put(domain, domainConn); + return this; + } + + public Map getProviders() { + return connections; + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/DomainHandlers.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/DomainHandlers.java similarity index 72% rename from starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/DomainHandlers.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/DomainHandlers.java index 002a7605..0704f715 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/DomainHandlers.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/DomainHandlers.java @@ -1,6 +1,7 @@ -package org.reactivecommons.async.kafka.config; +package org.reactivecommons.async.starter.config; import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; import java.util.Map; import java.util.TreeMap; @@ -15,7 +16,7 @@ public void add(String domain, HandlerResolver resolver) { public HandlerResolver get(String domain) { HandlerResolver handlerResolver = handlers.get(domain); if (handlerResolver == null) { - throw new RuntimeException("You are trying to use the domain " + domain + throw new InvalidConfigurationException("You are trying to use the domain " + domain + " but this connection is not defined"); } return handlerResolver; diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java new file mode 100644 index 00000000..40653de7 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java @@ -0,0 +1,138 @@ +package org.reactivecommons.async.starter.config; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DefaultCommandHandler; +import org.reactivecommons.async.api.DefaultQueryHandler; +import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolverBuilder; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.ext.DefaultCustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthConfig; +import org.reactivecommons.async.starter.props.GenericAsyncProps; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import reactor.core.publisher.Mono; + +import java.util.Map; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@Log +@Configuration +@RequiredArgsConstructor +@Import(ReactiveCommonsHealthConfig.class) +@ComponentScan("org.reactivecommons.async.starter.impl") +public class ReactiveCommonsConfig { + + @Bean + @SuppressWarnings({"rawtypes", "unchecked"}) + public ConnectionManager buildConnectionManager(ApplicationContext context) { + final Map props = context.getBeansOfType(GenericAsyncPropsDomain.class); + final Map providers = context.getBeansOfType(BrokerProviderFactory.class); + + ConnectionManager connectionManager = new ConnectionManager(); + props.forEach((beanName, domainProps) -> { + final GenericAsyncProps defaultDomainProps = domainProps.getProps(DEFAULT_DOMAIN); + domainProps.forEach((domain, asyncPropsObject) -> { + String domainName = (String) domain; + final GenericAsyncProps asyncProps = (GenericAsyncProps) asyncPropsObject; + if (asyncProps.isEnabled()) { + BrokerProviderFactory factory = providers.get(asyncProps.getBrokerType()); + if (!defaultDomainProps.isEnabled()) { + asyncProps.setUseDiscardNotifierPerDomain(true); + } + DiscardProvider discardProvider = factory.getDiscardProvider(defaultDomainProps); + BrokerProvider provider = factory.getProvider(domainName, asyncProps, discardProvider); + connectionManager.addDomain(domainName, provider); + } + }); + }); + return connectionManager; + } + + @Bean + @SuppressWarnings({"rawtypes", "unchecked"}) + public DomainHandlers buildHandlers(ApplicationContext context, + HandlerRegistry primaryRegistry, DefaultCommandHandler commandHandler) { + DomainHandlers handlers = new DomainHandlers(); + final Map registries = context.getBeansOfType(HandlerRegistry.class); + if (!registries.containsValue(primaryRegistry)) { + registries.put("primaryHandlerRegistry", primaryRegistry); + } + final Map props = context.getBeansOfType(GenericAsyncPropsDomain.class); + props.forEach((beanName, properties) -> properties.forEach((domain, asyncProps) -> { + String domainName = (String) domain; + HandlerResolver resolver = HandlerResolverBuilder.buildResolver(domainName, registries, commandHandler); + handlers.add(domainName, resolver); + })); + return handlers; + } + + @Bean + @ConditionalOnMissingBean + public BrokerConfig brokerConfig() { + return new BrokerConfig(); + } + + @Bean + @ConditionalOnMissingBean + public ObjectMapperSupplier objectMapperSupplier() { + return new DefaultObjectMapperSupplier(); + } + + @Bean + @ConditionalOnMissingBean + public CustomReporter reactiveCommonsCustomErrorReporter() { + return new DefaultCustomReporter(); + } + + @Bean + @ConditionalOnMissingBean + @SuppressWarnings("rawtypes") + public DefaultQueryHandler defaultHandler() { + return (DefaultQueryHandler) command -> + Mono.error(new RuntimeException("No Handler Registered")); + } + + @Bean + @ConditionalOnMissingBean + @SuppressWarnings("rawtypes") + public DefaultCommandHandler defaultCommandHandler() { + return message -> Mono.error(new RuntimeException("No Handler Registered")); + } + + @Bean + @ConditionalOnMissingBean(HandlerRegistry.class) + public HandlerRegistry defaultHandlerRegistry() { + return HandlerRegistry.register(); + } + + @Bean + @ConditionalOnMissingBean(ReactiveReplyRouter.class) + public ReactiveReplyRouter defaultReactiveReplyRouter() { + return new ReactiveReplyRouter(); + } + + @Bean + @ConditionalOnMissingBean(MeterRegistry.class) + public MeterRegistry defaultRabbitMeterRegistry() { + return new SimpleMeterRegistry(); + } + +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java new file mode 100644 index 00000000..c649942d --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java @@ -0,0 +1,20 @@ +package org.reactivecommons.async.starter.config.health; + +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnClass(AbstractReactiveHealthIndicator.class) +public class ReactiveCommonsHealthConfig { + + @Bean + @ConditionalOnProperty(prefix = "management.health.reactive-commons", name = "enabled", havingValue = "true", + matchIfMissing = true) + public ReactiveCommonsHealthIndicator reactiveCommonsHealthIndicator(ConnectionManager manager) { + return new ReactiveCommonsHealthIndicator(manager); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java new file mode 100644 index 00000000..624f651b --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java @@ -0,0 +1,38 @@ +package org.reactivecommons.async.starter.config.health; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Log4j2 +@AllArgsConstructor +public class ReactiveCommonsHealthIndicator extends AbstractReactiveHealthIndicator { + public static final String DOMAIN = "domain"; + public static final String VERSION = "version"; + private final ConnectionManager manager; + + @Override + @SuppressWarnings("unchecked") + protected Mono doHealthCheck(Health.Builder builder) { + return Flux.fromIterable(manager.getProviders().values()) + .flatMap(BrokerProvider::healthCheck) + .reduceWith(Health::up, (health, status) -> reduceHealth((Health.Builder) health, (Health) status)) + .map(b -> ((Health.Builder) b).build()); + + } + + private Health.Builder reduceHealth(Health.Builder builder, Health status) { + String domain = status.getDetails().get(DOMAIN).toString(); + if (status.getStatus().equals(Status.DOWN)) { + log.error("Broker of domain {} is down", domain); + return builder.down().withDetail(domain, status.getDetails()); + } + return builder.withDetail(domain, status.getDetails()); + } +} diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java similarity index 100% rename from starters/shared/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java new file mode 100644 index 00000000..e17d19e0 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java @@ -0,0 +1,16 @@ +package org.reactivecommons.async.starter.listeners; + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +public abstract class AbstractListenerConfig { + + protected AbstractListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + manager.forDomain((domain, provider) -> listen(domain, provider, handlers.get(domain))); + } + + @SuppressWarnings("rawtypes") + abstract void listen(String domain, BrokerProvider provider, HandlerResolver resolver); +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java new file mode 100644 index 00000000..7690d131 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(ReactiveCommonsConfig.class) +public class CommandsListenerConfig extends AbstractListenerConfig { + + public CommandsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenCommands(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java new file mode 100644 index 00000000..be7d075e --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(ReactiveCommonsConfig.class) +public class EventsListenerConfig extends AbstractListenerConfig { + + public EventsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenDomainEvents(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java new file mode 100644 index 00000000..647f7994 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(ReactiveCommonsConfig.class) +public class NotificationEventsListenerConfig extends AbstractListenerConfig { + + public NotificationEventsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenNotificationEvents(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java new file mode 100644 index 00000000..9710c770 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(ReactiveCommonsConfig.class) +public class QueriesListenerConfig extends AbstractListenerConfig { + + public QueriesListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenQueries(resolver); + } +} diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncProps.java similarity index 50% rename from starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncProps.java index f2b56b92..20dabcb1 100644 --- a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncProps.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.starter; +package org.reactivecommons.async.starter.props; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,7 +15,13 @@ public abstract class GenericAsyncProps

{ private String appName; private String secret; - abstract public void setConnectionProperties(P properties); + public abstract void setConnectionProperties(P properties); - abstract public P getConnectionProperties(); + public abstract P getConnectionProperties(); + + public abstract String getBrokerType(); + + public abstract boolean isEnabled(); + + public abstract void setUseDiscardNotifierPerDomain(boolean enabled); } \ No newline at end of file diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomain.java similarity index 80% rename from starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomain.java index 411abad8..a6717026 100644 --- a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomain.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.starter; +package org.reactivecommons.async.starter.props; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -10,6 +10,7 @@ import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @@ -32,7 +33,7 @@ public GenericAsyncPropsDomain(String defaultAppName, ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); this.computeIfAbsent(DEFAULT_DOMAIN, k -> { - T defaultApp = GenericAsyncPropsDomain.instantiate(asyncPropsClass); + T defaultApp = AsyncPropsDomainBuilder.instantiate(asyncPropsClass); defaultApp.setConnectionProperties(mapper.convertValue(defaultProperties, propsClass)); return defaultApp; }); @@ -61,7 +62,7 @@ public GenericAsyncPropsDomain(String defaultAppName, } protected void fillCustoms(T asyncProps) { - + // To be overridden called after the default properties are set } public T getProps(String domain) { @@ -79,11 +80,10 @@ public T getProps(String domain) { P, X extends GenericAsyncPropsDomainProperties, R extends GenericAsyncPropsDomain> - AsyncPropsDomainBuilder builder(Class asyncPropsClass, - Class

propsClass, + AsyncPropsDomainBuilder builder(Class

propsClass, Class asyncPropsDomainClass, Constructor returnType) { - return new AsyncPropsDomainBuilder<>(asyncPropsClass, propsClass, asyncPropsDomainClass, returnType); + return new AsyncPropsDomainBuilder<>(propsClass, asyncPropsDomainClass, returnType); } public static class AsyncPropsDomainBuilder< @@ -99,7 +99,7 @@ public static class AsyncPropsDomainBuilder< private P defaultProperties; private SecretFiller

secretFiller; - public AsyncPropsDomainBuilder(Class asynPropsClass, Class

propsClass, Class asyncPropsDomainClass, + public AsyncPropsDomainBuilder(Class

propsClass, Class asyncPropsDomainClass, Constructor returnType) { this.propsClass = propsClass; this.asyncPropsDomainClass = asyncPropsDomainClass; @@ -137,20 +137,33 @@ public R build() { return returnType.newInstance(defaultAppName, defaultProperties, domainProperties, secretFiller); } - } + @SneakyThrows + private static X instantiate(Class xClass) { + return xClass.getDeclaredConstructor().newInstance(); + } - @SneakyThrows - private static X instantiate(Class xClass) { - return xClass.getDeclaredConstructor().newInstance(); - } + @SneakyThrows + private static X instantiate(Class xClass, Map arg) { + return xClass.getDeclaredConstructor(Map.class).newInstance(arg); + } - @SneakyThrows - private static X instantiate(Class xClass, Map arg) { - return xClass.getDeclaredConstructor(Map.class).newInstance(arg); } public interface SecretFiller

{ void fillWithSecret(String domain, GenericAsyncProps

props); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + GenericAsyncPropsDomain that = (GenericAsyncPropsDomain) o; + return Objects.equals(asyncPropsClass, that.asyncPropsClass) && Objects.equals(propsClass, that.propsClass); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), asyncPropsClass, propsClass); + } } diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainProperties.java similarity index 82% rename from starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainProperties.java index f04336c4..c9a784f5 100644 --- a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainProperties.java @@ -1,7 +1,8 @@ -package org.reactivecommons.async.starter; +package org.reactivecommons.async.starter.props; import lombok.Getter; import lombok.Setter; +import lombok.SneakyThrows; import java.util.HashMap; import java.util.Map; @@ -10,7 +11,7 @@ @Setter public class GenericAsyncPropsDomainProperties, P> extends HashMap { - public GenericAsyncPropsDomainProperties(Map m) { + public GenericAsyncPropsDomainProperties(Map m) { super(m); } @@ -38,8 +39,9 @@ public AsyncPropsDomainPropertiesBuilder withDomain(String domain, T pr return this; } + @SneakyThrows public X build() { - return returnType.cast(new GenericAsyncPropsDomainProperties<>(domains)); + return returnType.getDeclaredConstructor(Map.class).newInstance(domains); } } } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java new file mode 100644 index 00000000..df6efea3 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.senders; + +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Log +@Configuration +@RequiredArgsConstructor +@Import(ReactiveCommonsConfig.class) +public class DirectAsyncGatewayConfig { + + @Bean + public DirectAsyncGateway genericDirectAsyncGateway(ConnectionManager manager, DomainHandlers handlers) { + ConcurrentMap directAsyncGateways = new ConcurrentHashMap<>(); + manager.forDomain((domain, provider) -> directAsyncGateways.put(domain, + provider.getDirectAsyncGateway(handlers.get(domain)))); + return new GenericDirectAsyncGateway(directAsyncGateways); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java new file mode 100644 index 00000000..b2b69e0b --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.starter.senders; + +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Configuration +@Import(ReactiveCommonsConfig.class) +public class EventBusConfig { + + @Bean + public DomainEventBus genericDomainEventBus(ConnectionManager manager) { + ConcurrentMap domainEventBuses = new ConcurrentHashMap<>(); + manager.forDomain((domain, provider) -> domainEventBuses.put(domain, provider.getDomainBus())); + return new GenericDomainEventBus(domainEventBuses); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java new file mode 100644 index 00000000..67acbf81 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java @@ -0,0 +1,83 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import lombok.AllArgsConstructor; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; +import reactor.core.publisher.Mono; + +import java.util.concurrent.ConcurrentMap; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@AllArgsConstructor +public class GenericDirectAsyncGateway implements DirectAsyncGateway { + private final ConcurrentMap directAsyncGateways; + + @Override + public Mono sendCommand(Command command, String targetName) { + return sendCommand(command, targetName, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis) { + return sendCommand(command, targetName, delayMillis, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(Command command, String targetName, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName, delayMillis); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName) { + return sendCommand(command, targetName, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis) { + return sendCommand(command, targetName, delayMillis, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName, delayMillis); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type) { + return requestReply(query, targetName, type, DEFAULT_DOMAIN); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type, String domain) { + return directAsyncGateways.get(domain).requestReply(query, targetName, type); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type) { + return requestReply(query, targetName, type, DEFAULT_DOMAIN); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { + return directAsyncGateways.get(domain).requestReply(query, targetName, type); + } + + @Override + public Mono reply(T response, From from) { + return directAsyncGateways.get(DEFAULT_DOMAIN).reply(response, from); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java new file mode 100644 index 00000000..0feda04a --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java @@ -0,0 +1,47 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +import java.util.concurrent.ConcurrentMap; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@RequiredArgsConstructor +public class GenericDomainEventBus implements DomainEventBus { + private final ConcurrentMap domainEventBuses; + + + @Override + public Publisher emit(DomainEvent event) { + return emit(DEFAULT_DOMAIN, event); + } + + @Override + public Publisher emit(String domain, DomainEvent event) { + DomainEventBus domainEventBus = domainEventBuses.get(domain); + if (domainEventBus == null) { + return Mono.error(() -> new InvalidConfigurationException("Domain not found: " + domain)); + } + return domainEventBus.emit(event); + } + + @Override + public Publisher emit(CloudEvent event) { + return emit(DEFAULT_DOMAIN, event); + } + + @Override + public Publisher emit(String domain, CloudEvent event) { + DomainEventBus domainEventBus = domainEventBuses.get(domain); + if (domainEventBus == null) { + return Mono.error(() -> new InvalidConfigurationException("Domain not found: " + domain)); + } + return domainEventBus.emit(event); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java new file mode 100644 index 00000000..34722ca4 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java @@ -0,0 +1,30 @@ +package org.reactivecommons.async.starter.config; + + +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.reactivecommons.async.starter.impl.mybroker.MyBrokerConfig; +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(classes = { + MyBrokerConfig.class, + ReactiveCommonsConfig.class +}) +class ReactiveCommonsConfigTest { + @Spy + @Autowired + private ApplicationContext context; + + @Test + void shouldCreateConnectionManager() { + // Arrange + // Act + ConnectionManager manager = context.getBean(ConnectionManager.class); + // Assert + assertNotNull(manager); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java new file mode 100644 index 00000000..d2c9b014 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java @@ -0,0 +1,74 @@ +package org.reactivecommons.async.starter.config.health; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; + +@ExtendWith(MockitoExtension.class) +class ReactiveCommonsHealthIndicatorTest { + public static final String OTHER = "other"; + @Mock + private BrokerProvider brokerProvider; + @Mock + private BrokerProvider brokerProvider2; + private ReactiveCommonsHealthIndicator healthIndicator; + + @BeforeEach + void setUp() { + ConnectionManager connectionManager = new ConnectionManager(); + connectionManager.addDomain(DEFAULT_DOMAIN, brokerProvider); + connectionManager.addDomain(OTHER, brokerProvider2); + ReactiveCommonsHealthConfig healthConfig = new ReactiveCommonsHealthConfig(); + healthIndicator = healthConfig.reactiveCommonsHealthIndicator(connectionManager); + } + + @Test + void shouldBeUp() { + // Arrange + when(brokerProvider.healthCheck()).thenReturn(Mono.just(Health.up() + .withDetail(DOMAIN, DEFAULT_DOMAIN) + .withDetail(VERSION, "123") + .build())); + when(brokerProvider2.healthCheck()).thenReturn(Mono.just(Health.up() + .withDetail(DOMAIN, OTHER) + .withDetail(VERSION, "1234") + .build())); + // Act + Mono flow = healthIndicator.health(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().toString().equals("UP")) + .verifyComplete(); + } + + @Test + void shouldBeDown() { + // Arrange + when(brokerProvider.healthCheck()).thenReturn(Mono.just(Health.up() + .withDetail(DOMAIN, DEFAULT_DOMAIN) + .withDetail(VERSION, "123") + .build())); + when(brokerProvider2.healthCheck()).thenReturn(Mono.just(Health.down() + .withDetail(DOMAIN, OTHER) + .withDetail(VERSION, "1234") + .build())); + // Act + Mono flow = healthIndicator.health(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().toString().equals("DOWN")) + .verifyComplete(); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java new file mode 100644 index 00000000..6fff5b03 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.impl.mybroker; + +import org.reactivecommons.async.starter.mybroker.MyBrokerProviderFactory; +import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.reactivecommons.async.starter.mybroker.props.AsyncMyBrokerPropsDomainProperties; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncPropsDomain; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +@EnableConfigurationProperties(AsyncMyBrokerPropsDomainProperties.class) +@Import({MyBrokerAsyncPropsDomain.class, MyBrokerProviderFactory.class}) +public class MyBrokerConfig { + + @Bean + public MyBrokerConnProps defaultMyBrokerConnProps() { + MyBrokerConnProps myBrokerConnProps = new MyBrokerConnProps(); + myBrokerConnProps.setHost("localhost"); + myBrokerConnProps.setPort("1234"); + return myBrokerConnProps; + } + + @Bean + public MyBrokerSecretFiller defaultMyBrokerSecretFiller() { + return (domain, props) -> { + }; + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java new file mode 100644 index 00000000..986c0081 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class CommandsListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new CommandsListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenCommands(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java new file mode 100644 index 00000000..bfcab084 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class EventsListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new EventsListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenDomainEvents(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java new file mode 100644 index 00000000..ece9dbd6 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class NotificationEventsListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new NotificationEventsListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenNotificationEvents(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java new file mode 100644 index 00000000..d327c9a1 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class QueriesListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new QueriesListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenQueries(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java new file mode 100644 index 00000000..95f5e976 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java @@ -0,0 +1,63 @@ +package org.reactivecommons.async.starter.mybroker; + +import lombok.AllArgsConstructor; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; + +@AllArgsConstructor +public class MyBrokerProvider implements BrokerProvider { + private final String domain; + private final MyBrokerAsyncProps props; + private final DiscardProvider discardProvider; + + @Override + public MyBrokerAsyncProps getProps() { + return null; + } + + @Override + public DomainEventBus getDomainBus() { + return null; + } + + @Override + public DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver) { + return null; + } + + @Override + public void listenDomainEvents(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenNotificationEvents(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenCommands(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenQueries(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenReplies(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public Mono healthCheck() { + return null; + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java new file mode 100644 index 00000000..edecf27e --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java @@ -0,0 +1,27 @@ +package org.reactivecommons.async.starter.mybroker; + +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service("mybroker") +public class MyBrokerProviderFactory implements BrokerProviderFactory { + + @Override + public String getBrokerType() { + return "mybroker"; + } + + @Override + public DiscardProvider getDiscardProvider(MyBrokerAsyncProps props) { + return () -> message -> Mono.empty(); + } + + @Override + public BrokerProvider getProvider(String domain, MyBrokerAsyncProps props, DiscardProvider discardProvider) { + return new MyBrokerProvider(domain, props, discardProvider); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java new file mode 100644 index 00000000..2af3a2e8 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.starter.mybroker; + +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; + +public interface MyBrokerSecretFiller extends GenericAsyncPropsDomain.SecretFiller { +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java new file mode 100644 index 00000000..792ef21e --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@ConfigurationProperties(prefix = "my.broker") +public class AsyncMyBrokerPropsDomainProperties + extends GenericAsyncPropsDomainProperties { + + public AsyncMyBrokerPropsDomainProperties() { + } + + public AsyncMyBrokerPropsDomainProperties(Map m) { + super(m); + } + + public static AsyncPropsDomainPropertiesBuilder builder() { + return GenericAsyncPropsDomainProperties.builder(AsyncMyBrokerPropsDomainProperties.class); + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java new file mode 100644 index 00000000..0b5666ef --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java @@ -0,0 +1,22 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.reactivecommons.async.starter.props.GenericAsyncProps; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class MyBrokerAsyncProps extends GenericAsyncProps { + private MyBrokerConnProps connectionProperties; + @Builder.Default + private String brokerType = "mybroker"; + private boolean enabled; + private boolean useDiscardNotifierPerDomain; +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java new file mode 100644 index 00000000..d2239eb0 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java @@ -0,0 +1,20 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import lombok.Getter; +import lombok.Setter; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.springframework.beans.factory.annotation.Value; + +@Getter +@Setter +public class MyBrokerAsyncPropsDomain extends GenericAsyncPropsDomain { + + public MyBrokerAsyncPropsDomain(@Value("${spring.application.name}") String defaultAppName, + MyBrokerConnProps defaultRabbitProperties, + AsyncMyBrokerPropsDomainProperties configured, + MyBrokerSecretFiller secretFiller) { + super(defaultAppName, defaultRabbitProperties, configured, secretFiller, MyBrokerAsyncProps.class, + MyBrokerConnProps.class); + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java new file mode 100644 index 00000000..29ba7345 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java @@ -0,0 +1,9 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import lombok.Data; + +@Data +public class MyBrokerConnProps { + private String host; + private String port; +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java new file mode 100644 index 00000000..8e4dac67 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java @@ -0,0 +1,98 @@ +package org.reactivecommons.async.starter.props; + +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; +import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.reactivecommons.async.starter.mybroker.props.AsyncMyBrokerPropsDomainProperties; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncPropsDomain; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; + +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@SuppressWarnings("unchecked") +class GenericAsyncPropsDomainTest { + + public static final String OTHER = "other"; + + @Test + void shouldCreateProps() { + // Arrange + String defaultAppName = "sample"; + MyBrokerConnProps defaultMyBrokerProps = new MyBrokerConnProps(); + AsyncMyBrokerPropsDomainProperties configured = new AsyncMyBrokerPropsDomainProperties(); + MyBrokerAsyncProps other = new MyBrokerAsyncProps(); + other.setAppName(OTHER); + configured.put(OTHER, other); + MyBrokerSecretFiller secretFiller = (domain, props) -> { + }; + MyBrokerAsyncPropsDomain propsDomain = new MyBrokerAsyncPropsDomain(defaultAppName, defaultMyBrokerProps, configured, + secretFiller); + // Act + MyBrokerAsyncProps props = propsDomain.getProps(DEFAULT_DOMAIN); + MyBrokerAsyncProps otherProps = propsDomain.getProps(OTHER); + // Assert + assertEquals("sample", props.getAppName()); + assertEquals(OTHER, otherProps.getAppName()); + assertThrows(InvalidConfigurationException.class, () -> propsDomain.getProps("non-existing-domain")); + } + + @Test + void shouldCreatePropsWithDefaultConnectionProperties() { + // Arrange + String defaultAppName = "sample"; + MyBrokerConnProps defaultMyBrokerProps = new MyBrokerConnProps(); + MyBrokerAsyncProps propsConfigured = new MyBrokerAsyncProps(); + MyBrokerAsyncPropsDomain propsDomain = MyBrokerAsyncPropsDomain.builder(MyBrokerConnProps.class, + AsyncMyBrokerPropsDomainProperties.class, + (Constructor) MyBrokerAsyncPropsDomain.class.getDeclaredConstructors()[0]) + .withDefaultAppName(defaultAppName) + .withDefaultProperties(defaultMyBrokerProps) + .withDomain(DEFAULT_DOMAIN, propsConfigured) + .withSecretFiller(null) + .build(); + // Act + MyBrokerAsyncProps props = propsDomain.getProps(DEFAULT_DOMAIN); + // Assert + assertEquals("sample", props.getAppName()); + assertEquals(defaultMyBrokerProps, props.getConnectionProperties()); + } + + @Test + void shouldFailCreatePropsWhenAppNameIsNullOrEmpty() { + // Arrange + MyBrokerConnProps defaultMyBrokerProps = new MyBrokerConnProps(); + AsyncMyBrokerPropsDomainProperties configured = new AsyncMyBrokerPropsDomainProperties(); + MyBrokerSecretFiller secretFiller = (domain, props) -> { + }; + // Assert + assertThrows(InvalidConfigurationException.class, + // Act + () -> new MyBrokerAsyncPropsDomain(null, defaultMyBrokerProps, configured, secretFiller)); + assertThrows(InvalidConfigurationException.class, + // Act + () -> new MyBrokerAsyncPropsDomain("", defaultMyBrokerProps, configured, secretFiller)); + } + + @Test + void shouldFailCreatePropsWhenDefaultConnectionPropertiesAreNul() { + // Arrange + String defaultAppName = "sample"; + AsyncMyBrokerPropsDomainProperties configured = AsyncMyBrokerPropsDomainProperties + .builder(AsyncMyBrokerPropsDomainProperties.class) + .withDomain(OTHER, new MyBrokerAsyncProps()) + .build(); + MyBrokerSecretFiller secretFiller = (domain, props) -> { + }; + // Assert + assertThrows(InvalidConfigurationException.class, + // Act + () -> new MyBrokerAsyncPropsDomain(defaultAppName, null, configured, secretFiller)); + } + + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java new file mode 100644 index 00000000..6a02949c --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java @@ -0,0 +1,53 @@ +package org.reactivecommons.async.starter.senders; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DirectAsyncGatewayConfigTest { + @Mock + private DirectAsyncGateway domainEventBus; + @Mock + private BrokerProvider brokerProvider; + @Mock + private HandlerResolver resolver; + + private ConnectionManager manager; + private DirectAsyncGatewayConfig directAsyncGatewayConfig; + + @BeforeEach + void setUp() { + directAsyncGatewayConfig = new DirectAsyncGatewayConfig(); + manager = new ConnectionManager(); + manager.addDomain("domain", brokerProvider); + manager.addDomain("domain2", brokerProvider); + } + + @Test + void shouldCreateAllDomainEventBuses() { + // Arrange + when(brokerProvider.getDirectAsyncGateway(any())).thenReturn(domainEventBus); + DomainHandlers handlers = new DomainHandlers(); + handlers.add("domain", resolver); + handlers.add("domain2", resolver); + // Act + DirectAsyncGateway genericDomainEventBus = directAsyncGatewayConfig.genericDirectAsyncGateway(manager, handlers); + // Assert + assertNotNull(genericDomainEventBus); + verify(brokerProvider, times(2)).getDirectAsyncGateway(resolver); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java new file mode 100644 index 00000000..1be56082 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java @@ -0,0 +1,45 @@ +package org.reactivecommons.async.starter.senders; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class EventBusConfigTest { + @Mock + private DomainEventBus domainEventBus; + @Mock + private BrokerProvider brokerProvider; + + private ConnectionManager manager; + private EventBusConfig eventBusConfig; + + @BeforeEach + void setUp() { + eventBusConfig = new EventBusConfig(); + manager = new ConnectionManager(); + manager.addDomain("domain", brokerProvider); + manager.addDomain("domain2", brokerProvider); + } + + @Test + void shouldCreateAllDomainEventBuses() { + // Arrange + when(brokerProvider.getDomainBus()).thenReturn(domainEventBus); + // Act + DomainEventBus genericDomainEventBus = eventBusConfig.genericDomainEventBus(manager); + // Assert + assertNotNull(genericDomainEventBus); + verify(brokerProvider, times(2)).getDomainBus(); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java new file mode 100644 index 00000000..5b6ca9bd --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java @@ -0,0 +1,161 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class GenericDirectAsyncGatewayTest { + + public static final String DOMAIN_2 = "domain2"; + + @Mock + private DirectAsyncGateway directAsyncGateway1; + + @Mock + private DirectAsyncGateway directAsyncGateway2; + + @Mock + private CloudEvent cloudEvent; + + @Mock + private Command command; + + @Mock + private AsyncQuery asyncQuery; + + @Mock + private From from; + + private final long delayMillis = 100L; + + private GenericDirectAsyncGateway genericDirectAsyncGateway; + + @BeforeEach + void setUp() { + ConcurrentHashMap directAsyncGateways = new ConcurrentHashMap<>(); + directAsyncGateways.put(DEFAULT_DOMAIN, directAsyncGateway1); + directAsyncGateways.put(DOMAIN_2, directAsyncGateway2); + genericDirectAsyncGateway = new GenericDirectAsyncGateway(directAsyncGateways); + } + + @Test + void shouldSendCommandWithDefaultDomain() { + when(directAsyncGateway1.sendCommand(command, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target"); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(command, "target"); + } + + @Test + void shouldSendCommandWithDefaultDomainWithDelay() { + when(directAsyncGateway1.sendCommand(command, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target", delayMillis); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(command, "target", delayMillis); + } + + @Test + void shouldSendCommandWithSpecificDomain() { + when(directAsyncGateway2.sendCommand(command, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target", DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(command, "target"); + } + + @Test + void shouldSendCommandWithSpecificDomainWithDelay() { + when(directAsyncGateway2.sendCommand(command, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target", delayMillis, DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(command, "target", delayMillis); + } + + @Test + void shouldSendCloudEventWithDefaultDomain() { + when(directAsyncGateway1.sendCommand(cloudEvent, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target"); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(cloudEvent, "target"); + } + + @Test + void shouldSendCloudEventWithDefaultDomainWithDelay() { + when(directAsyncGateway1.sendCommand(cloudEvent, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target", delayMillis); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(cloudEvent, "target", delayMillis); + } + + @Test + void shouldSendCloudEventWithSpecificDomain() { + when(directAsyncGateway2.sendCommand(cloudEvent, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target", DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(cloudEvent, "target"); + } + + @Test + void shouldSendCloudEventWithSpecificDomainWithDelay() { + when(directAsyncGateway2.sendCommand(cloudEvent, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target", delayMillis, DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(cloudEvent, "target", delayMillis); + } + + @Test + void shouldRequestReplyWithDefaultDomain() { + when(directAsyncGateway1.requestReply(asyncQuery, "target", String.class)).thenReturn(Mono.just("response")); + Mono flow = genericDirectAsyncGateway.requestReply(asyncQuery, "target", String.class); + StepVerifier.create(flow).expectNext("response").verifyComplete(); + verify(directAsyncGateway1).requestReply(asyncQuery, "target", String.class); + } + + + @Test + void shouldRequestReplyWithDefaultDomainCloudEvent() { + when(directAsyncGateway1.requestReply(cloudEvent, "target", CloudEvent.class)).thenReturn(Mono.just(cloudEvent)); + Mono flow = genericDirectAsyncGateway.requestReply(cloudEvent, "target", CloudEvent.class); + StepVerifier.create(flow).expectNext(cloudEvent).verifyComplete(); + verify(directAsyncGateway1).requestReply(cloudEvent, "target", CloudEvent.class); + } + + @Test + void shouldRequestReplyWithSpecificDomain() { + when(directAsyncGateway2.requestReply(asyncQuery, "target", String.class)).thenReturn(Mono.just("response")); + Mono flow = genericDirectAsyncGateway.requestReply(asyncQuery, "target", String.class, DOMAIN_2); + StepVerifier.create(flow).expectNext("response").verifyComplete(); + verify(directAsyncGateway2).requestReply(asyncQuery, "target", String.class); + } + + @Test + void shouldRequestReplyWithSpecificDomainCloudEvent() { + when(directAsyncGateway2.requestReply(cloudEvent, "target", CloudEvent.class)).thenReturn(Mono.just(cloudEvent)); + Mono flow = genericDirectAsyncGateway.requestReply(cloudEvent, "target", CloudEvent.class, DOMAIN_2); + StepVerifier.create(flow).expectNext(cloudEvent).verifyComplete(); + verify(directAsyncGateway2).requestReply(cloudEvent, "target", CloudEvent.class); + } + + @Test + void shouldReplyWithDefaultDomain() { + when(directAsyncGateway1.reply("response", from)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.reply("response", from); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).reply("response", from); + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java new file mode 100644 index 00000000..c2f23fe6 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java @@ -0,0 +1,111 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class GenericDomainEventBusTest { + public static final String DOMAIN_2 = "domain2"; + @Mock + private DomainEventBus domainEventBus1; + @Mock + private DomainEventBus domainEventBus2; + @Mock + private CloudEvent cloudEvent; + @Mock + private DomainEvent domainEvent; + private GenericDomainEventBus genericDomainEventBus; + + @BeforeEach + void setUp() { + ConcurrentHashMap domainEventBuses = new ConcurrentHashMap<>(); + domainEventBuses.put(DEFAULT_DOMAIN, domainEventBus1); + domainEventBuses.put(DOMAIN_2, domainEventBus2); + genericDomainEventBus = new GenericDomainEventBus(domainEventBuses); + } + + @Test + void shouldEmitWithDefaultDomain() { + // Arrange + when(domainEventBus1.emit(domainEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus1).emit(domainEvent); + } + + @Test + void shouldEmitCloudEventWithDefaultDomain() { + // Arrange + when(domainEventBus1.emit(cloudEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus1).emit(cloudEvent); + } + + @Test + void shouldEmitWithSpecificDomain() { + // Arrange + when(domainEventBus2.emit(domainEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(DOMAIN_2, domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus2).emit(domainEvent); + } + + @Test + void shouldEmitCloudEventWithSpecificDomain() { + // Arrange + when(domainEventBus2.emit(cloudEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(DOMAIN_2, cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus2).emit(cloudEvent); + } + + @Test + void shouldFailWhenNoDomainFound() { + // Arrange + // Act + Mono flow = Mono.from(genericDomainEventBus.emit("another", domainEvent)); + // Assert + StepVerifier.create(flow) + .expectError(InvalidConfigurationException.class) + .verify(); + } + + @Test + void shouldFailWhenNoDomainFoundWithCloudEvent() { + // Arrange + // Act + Mono flow = Mono.from(genericDomainEventBus.emit("another", cloudEvent)); + // Assert + StepVerifier.create(flow) + .expectError(InvalidConfigurationException.class) + .verify(); + } +} diff --git a/starters/async-commons-starter/src/test/resources/application.yaml b/starters/async-commons-starter/src/test/resources/application.yaml new file mode 100644 index 00000000..30c7fd10 --- /dev/null +++ b/starters/async-commons-starter/src/test/resources/application.yaml @@ -0,0 +1,7 @@ +spring: + application: + name: test-app +my: + broker: + app: + enabled: true \ No newline at end of file diff --git a/starters/async-kafka-starter/async-kafka-starter.gradle b/starters/async-kafka-starter/async-kafka-starter.gradle index 41ba255e..49439d3c 100644 --- a/starters/async-kafka-starter/async-kafka-starter.gradle +++ b/starters/async-kafka-starter/async-kafka-starter.gradle @@ -5,7 +5,8 @@ ext { dependencies { api project(':async-kafka') - api project(':shared-starter') + api project(':async-commons-starter') + implementation 'org.apache.kafka:kafka-clients' compileOnly 'org.springframework.boot:spring-boot-starter' compileOnly 'org.springframework.boot:spring-boot-starter-actuator' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java new file mode 100644 index 00000000..b2ac1965 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java @@ -0,0 +1,106 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.kafka.health.KafkaReactiveHealthIndicator; +import org.reactivecommons.async.kafka.listeners.ApplicationEventListener; +import org.reactivecommons.async.kafka.listeners.ApplicationNotificationsListener; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.ssl.SslBundles; +import reactor.core.publisher.Mono; + +@Getter +@AllArgsConstructor +public class KafkaBrokerProvider implements BrokerProvider { + private final String domain; + private final AsyncKafkaProps props; + private final ReactiveReplyRouter router; + private final KafkaJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final KafkaReactiveHealthIndicator healthIndicator; + private final ReactiveMessageListener receiver; + private final ReactiveMessageSender sender; + private final DiscardNotifier discardNotifier; + private final TopologyCreator topologyCreator; + private final KafkaCustomizations customizations; + private final SslBundles sslBundles; + + @Override + public DomainEventBus getDomainBus() { + return new KafkaDomainEventBus(sender); + } + + @Override + public DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver) { + return new KafkaDirectAsyncGateway(); + } + + @Override + public void listenDomainEvents(HandlerResolver resolver) { + if (!props.getDomain().isIgnoreThisListener() && !resolver.getEventListeners().isEmpty()) { + ApplicationEventListener eventListener = new ApplicationEventListener(receiver, + resolver, + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + discardNotifier, + errorReporter, + props.getAppName()); + eventListener.startListener(topologyCreator); + } + } + + @Override + public void listenNotificationEvents(HandlerResolver resolver) { + if (!resolver.getNotificationListeners().isEmpty()) { + ApplicationNotificationsListener notificationEventListener = new ApplicationNotificationsListener(receiver, + resolver, + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + discardNotifier, + errorReporter, + props.getAppName()); + notificationEventListener.startListener(topologyCreator); + } + } + + @Override + public void listenCommands(HandlerResolver resolver) { + // Implemented in the future + } + + @Override + public void listenQueries(HandlerResolver resolver) { + // May be implemented in the future + } + + @Override + public void listenReplies(HandlerResolver resolver) { + // May be implemented in the future + } + + @Override + public Mono healthCheck() { + return healthIndicator.health(); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java new file mode 100644 index 00000000..0c06949a --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java @@ -0,0 +1,59 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.AllArgsConstructor; +import org.apache.kafka.clients.admin.AdminClient; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.kafka.health.KafkaReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.stereotype.Service; + +@Service("kafka") +@AllArgsConstructor +public class KafkaBrokerProviderFactory implements BrokerProviderFactory { + private final ReactiveReplyRouter router; + private final KafkaJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final KafkaCustomizations customizations; + private final SslBundles sslBundles; + + @Override + public String getBrokerType() { + return "kafka"; + } + + @Override + public DiscardProvider getDiscardProvider(AsyncKafkaProps props) { + return new KafkaDiscardProvider(props, converter, customizations, sslBundles); + } + + @Override + public BrokerProvider getProvider(String domain, AsyncKafkaProps props, DiscardProvider discardProvider) { + TopologyCreator creator = KafkaSetupUtils.createTopologyCreator(props, customizations, sslBundles); + ReactiveMessageSender sender = KafkaSetupUtils.createMessageSender(props, converter, creator, sslBundles); + ReactiveMessageListener listener = KafkaSetupUtils.createMessageListener(props, sslBundles); + AdminClient adminClient = AdminClient.create(props.getConnectionProperties().buildAdminProperties(sslBundles)); + KafkaReactiveHealthIndicator healthIndicator = new KafkaReactiveHealthIndicator(domain, adminClient); + DiscardNotifier discardNotifier; + if (props.isUseDiscardNotifierPerDomain()) { + discardNotifier = KafkaSetupUtils.createDiscardNotifier(sender, converter); + } else { + discardNotifier = discardProvider.get(); + } + return new KafkaBrokerProvider(domain, props, router, converter, meterRegistry, errorReporter, + healthIndicator, listener, sender, discardNotifier, creator, customizations, sslBundles); + } + +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java new file mode 100644 index 00000000..11307a8b --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java @@ -0,0 +1,34 @@ +package org.reactivecommons.async.kafka; + +import lombok.AllArgsConstructor; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.boot.ssl.SslBundles; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@AllArgsConstructor +public class KafkaDiscardProvider implements DiscardProvider { + private final AsyncKafkaProps props; + private final MessageConverter converter; + private final KafkaCustomizations customizations; + private final SslBundles sslBundles; + private final Map discardNotifier = new ConcurrentHashMap<>(); + + @Override + public DiscardNotifier get() { + return discardNotifier.computeIfAbsent(true, this::buildDiscardNotifier); + } + + private DiscardNotifier buildDiscardNotifier(boolean ignored) { + TopologyCreator creator = KafkaSetupUtils.createTopologyCreator(props, customizations, sslBundles); + ReactiveMessageSender sender = KafkaSetupUtils.createMessageSender(props, converter, creator, sslBundles); + return KafkaSetupUtils.createDiscardNotifier(sender, converter); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java new file mode 100644 index 00000000..c6670307 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java @@ -0,0 +1,86 @@ +package org.reactivecommons.async.kafka; + +import lombok.experimental.UtilityClass; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.common.serialization.ByteArrayDeserializer; +import org.apache.kafka.common.serialization.ByteArraySerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.springframework.boot.ssl.SslBundles; +import reactor.kafka.receiver.ReceiverOptions; +import reactor.kafka.sender.KafkaSender; +import reactor.kafka.sender.SenderOptions; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@UtilityClass +public class KafkaSetupUtils { + + public static DiscardNotifier createDiscardNotifier(ReactiveMessageSender sender, MessageConverter converter) { + return new DLQDiscardNotifier(new KafkaDomainEventBus(sender), converter); + } + + + public static ReactiveMessageSender createMessageSender(AsyncKafkaProps config, + MessageConverter converter, + TopologyCreator topologyCreator, + SslBundles sslBundles) { + KafkaProperties props = config.getConnectionProperties(); + props.setClientId(config.getAppName()); + props.getProducer().setKeySerializer(StringSerializer.class); + props.getProducer().setValueSerializer(ByteArraySerializer.class); + SenderOptions senderOptions = SenderOptions.create(props.buildProducerProperties(sslBundles)); + KafkaSender kafkaSender = KafkaSender.create(senderOptions); + return new ReactiveMessageSender(kafkaSender, converter, topologyCreator); + } + + // Receiver + + public static ReactiveMessageListener createMessageListener(AsyncKafkaProps config, SslBundles sslBundles) { + KafkaProperties props = config.getConnectionProperties(); + props.getConsumer().setKeyDeserializer(StringDeserializer.class); + props.getConsumer().setValueDeserializer(ByteArrayDeserializer.class); + ReceiverOptions receiverOptions = ReceiverOptions.create(props.buildConsumerProperties(sslBundles)); + return new ReactiveMessageListener(receiverOptions); + } + + // Shared + public static TopologyCreator createTopologyCreator(AsyncKafkaProps config, KafkaCustomizations customizations, + SslBundles sslBundles) { + AdminClient adminClient = AdminClient.create(config.getConnectionProperties().buildAdminProperties(sslBundles)); + return new TopologyCreator(adminClient, customizations, config.getCheckExistingTopics()); + } + + // Utilities + + public static KafkaProperties readPropsFromDotEnv(Path path) throws IOException { + String env = Files.readString(path); + String[] split = env.split("\n"); + KafkaProperties props = new KafkaProperties(); + Map properties = props.getProperties(); + for (String s : split) { + if (s.startsWith("#")) { + continue; + } + String[] split1 = s.split("=", 2); + properties.put(split1[0], split1[1]); + } + return props; + } + + public static String jassConfig(String username, String password) { + return String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";", username, password); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/ConnectionManager.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/ConnectionManager.java deleted file mode 100644 index 83d0425b..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/ConnectionManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.reactivecommons.async.kafka.config; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; -import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; -import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; - -import java.util.Map; -import java.util.TreeMap; -import java.util.function.BiConsumer; - -public class ConnectionManager { - private final Map connections = new TreeMap<>(); - - @Builder - @Getter - public static class DomainConnections { - private final ReactiveMessageListener listener; - private final ReactiveMessageSender sender; - private final TopologyCreator topologyCreator; - @Setter - private DiscardNotifier discardNotifier; - } - - public void forSender(BiConsumer consumer) { - connections.forEach((key, conn) -> consumer.accept(key, conn.getSender())); - } - - public void forListener(BiConsumer consumer) { - connections.forEach((key, conn) -> consumer.accept(key, conn.getListener())); - } - - public void setDiscardNotifier(String domain, DiscardNotifier discardNotifier) { - getChecked(domain).setDiscardNotifier(discardNotifier); - } - - public ConnectionManager addDomain(String domain, ReactiveMessageListener listener, ReactiveMessageSender sender, - TopologyCreator topologyCreator) { - connections.put(domain, DomainConnections.builder() - .listener(listener) - .sender(sender) - .topologyCreator(topologyCreator) - .build()); - return this; - } - - public ReactiveMessageSender getSender(String domain) { - return getChecked(domain).getSender(); - } - - public ReactiveMessageListener getListener(String domain) { - return getChecked(domain).getListener(); - } - - private DomainConnections getChecked(String domain) { - DomainConnections domainConnections = connections.get(domain); - if (domainConnections == null) { - throw new RuntimeException("You are trying to use the domain " + domain - + " but this connection is not defined"); - } - return domainConnections; - } - - public DiscardNotifier getDiscardNotifier(String domain) { - return getChecked(domain).getDiscardNotifier(); - } - - public TopologyCreator getTopologyCreator(String domain) { - return getChecked(domain).getTopologyCreator(); - } - - public Map getProviders() { - return connections; - } -} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaConfig.java deleted file mode 100644 index 950cb497..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaConfig.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.reactivecommons.async.kafka.config; - -import org.apache.kafka.clients.admin.AdminClient; -import org.apache.kafka.common.serialization.ByteArrayDeserializer; -import org.apache.kafka.common.serialization.ByteArraySerializer; -import org.apache.kafka.common.serialization.StringDeserializer; -import org.apache.kafka.common.serialization.StringSerializer; -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.DLQDiscardNotifier; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.HandlerResolverBuilder; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; -import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.commons.ext.DefaultCustomReporter; -import org.reactivecommons.async.kafka.KafkaDomainEventBus; -import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; -import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; -import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; -import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; -import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.ssl.SslBundles; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import reactor.core.publisher.Mono; -import reactor.kafka.receiver.ReceiverOptions; -import reactor.kafka.sender.KafkaSender; -import reactor.kafka.sender.SenderOptions; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@EnableConfigurationProperties({KafkaPropertiesAutoConfig.class, AsyncKafkaPropsDomainProperties.class}) -@Import(AsyncKafkaPropsDomain.class) // RabbitHealthConfig.class -public class RCKafkaConfig { - - @Bean - public ConnectionManager kafkaConnectionManager(AsyncKafkaPropsDomain props, - MessageConverter converter, - KafkaCustomizations customizations, - SslBundles sslBundles) { - ConnectionManager connectionManager = new ConnectionManager(); - props.forEach((domain, properties) -> { - TopologyCreator creator = createTopologyCreator(properties, customizations, sslBundles); - ReactiveMessageSender sender = createMessageSender(properties, converter, creator, sslBundles); - ReactiveMessageListener listener = createMessageListener(properties, sslBundles); - connectionManager.addDomain(domain, listener, sender, creator); - - ReactiveMessageSender appDomainSender = connectionManager.getSender(domain); - DomainEventBus appDomainEventBus = new KafkaDomainEventBus(appDomainSender); - DiscardNotifier notifier = new DLQDiscardNotifier(appDomainEventBus, converter); - connectionManager.setDiscardNotifier(domain, notifier); - }); - return connectionManager; - } - - // Sender - @Bean - @ConditionalOnMissingBean(DomainEventBus.class) - public DomainEventBus kafkaDomainEventBus(ConnectionManager manager) { - return new KafkaDomainEventBus(manager.getSender(DEFAULT_DOMAIN)); - } - - private static ReactiveMessageSender createMessageSender(AsyncKafkaProps config, - MessageConverter converter, - TopologyCreator topologyCreator, - SslBundles sslBundles) { - KafkaProperties props = config.getConnectionProperties(); - props.setClientId(config.getAppName()); // CLIENT_ID_CONFIG - props.getProducer().setKeySerializer(StringSerializer.class); // KEY_SERIALIZER_CLASS_CONFIG; - props.getProducer().setValueSerializer(ByteArraySerializer.class); // VALUE_SERIALIZER_CLASS_CONFIG - SenderOptions senderOptions = SenderOptions.create(props.buildProducerProperties(sslBundles)); - KafkaSender kafkaSender = KafkaSender.create(senderOptions); - return new ReactiveMessageSender(kafkaSender, converter, topologyCreator); - } - - // Receiver - - private static ReactiveMessageListener createMessageListener(AsyncKafkaProps config, SslBundles sslBundles) { - KafkaProperties props = config.getConnectionProperties(); - props.getConsumer().setKeyDeserializer(StringDeserializer.class); // KEY_DESERIALIZER_CLASS_CONFIG - props.getConsumer().setValueDeserializer(ByteArrayDeserializer.class); // VALUE_DESERIALIZER_CLASS_CONFIG - ReceiverOptions receiverOptions = ReceiverOptions.create(props.buildConsumerProperties(sslBundles)); - return new ReactiveMessageListener(receiverOptions); - } - - // Shared - private static TopologyCreator createTopologyCreator(AsyncKafkaProps config, KafkaCustomizations customizations, - SslBundles sslBundles) { - AdminClient adminClient = AdminClient.create(config.getConnectionProperties().buildAdminProperties(sslBundles)); - return new TopologyCreator(adminClient, customizations, config.getCheckExistingTopics()); - } - - @Bean - @ConditionalOnMissingBean(KafkaCustomizations.class) - public KafkaCustomizations defaultKafkaCustomizations() { - return new KafkaCustomizations(); - } - - @Bean - @ConditionalOnMissingBean(MessageConverter.class) - public MessageConverter kafkaJacksonMessageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new KafkaJacksonMessageConverter(objectMapperSupplier.get()); - } - - @Bean - @ConditionalOnMissingBean(DiscardNotifier.class) - public DiscardNotifier kafkaDiscardNotifier(DomainEventBus domainEventBus, MessageConverter messageConverter) { - return new DLQDiscardNotifier(domainEventBus, messageConverter); - } - - @Bean - @ConditionalOnMissingBean(ObjectMapperSupplier.class) - public ObjectMapperSupplier defaultObjectMapperSupplier() { - return new DefaultObjectMapperSupplier(); - } - - @Bean - @ConditionalOnMissingBean(CustomReporter.class) - public CustomReporter defaultKafkaCustomReporter() { - return new DefaultCustomReporter(); - } - - @Bean - @ConditionalOnMissingBean(AsyncKafkaPropsDomain.KafkaSecretFiller.class) - public AsyncKafkaPropsDomain.KafkaSecretFiller defaultKafkaSecretFiller() { - return (ignoredDomain, ignoredProps) -> { - }; - } - - @Bean - @ConditionalOnMissingBean(KafkaProperties.class) - public KafkaProperties defaultKafkaProperties(KafkaPropertiesAutoConfig properties, ObjectMapperSupplier supplier) { - return supplier.get().convertValue(properties, KafkaProperties.class); - } - - @Bean - @ConditionalOnMissingBean(DefaultCommandHandler.class) - public DefaultCommandHandler defaultCommandHandler() { - return command -> Mono.empty(); - } - - // Utilities - - public static KafkaProperties readPropsFromDotEnv(Path path) throws IOException { - String env = Files.readString(path); - String[] split = env.split("\n"); - KafkaProperties props = new KafkaProperties(); - Map properties = props.getProperties(); - for (String s : split) { - if (s.startsWith("#")) { - continue; - } - String[] split1 = s.split("=", 2); - properties.put(split1[0], split1[1]); - } - return props; - } - - public static String jassConfig(String username, String password) { - return String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";", username, password); - } -} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaEventListenerConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaEventListenerConfig.java deleted file mode 100644 index 47ee16c2..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaEventListenerConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.reactivecommons.async.kafka.config; - -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; -import org.reactivecommons.async.kafka.listeners.ApplicationEventListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -public class RCKafkaEventListenerConfig { - - @Bean - public ApplicationEventListener kafkaEventListener(ConnectionManager manager, - DomainHandlers handlers, - AsyncKafkaPropsDomain asyncPropsDomain, - MessageConverter messageConverter, - CustomReporter customReporter) { - AtomicReference external = new AtomicReference<>(); - manager.forListener((domain, receiver) -> { - AsyncKafkaProps asyncProps = asyncPropsDomain.getProps(domain); - if (!asyncProps.getDomain().isIgnoreThisListener()) { - ApplicationEventListener eventListener = new ApplicationEventListener(receiver, - handlers.get(domain), - messageConverter, - asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), - manager.getDiscardNotifier(domain), - customReporter, - asyncProps.getAppName()); - if (DEFAULT_DOMAIN.equals(domain)) { - external.set(eventListener); - } - - eventListener.startListener(manager.getTopologyCreator(domain)); - } - }); - - return external.get(); - } - -} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaHandlersConfiguration.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaHandlersConfiguration.java deleted file mode 100644 index 7eecd274..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaHandlersConfiguration.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.reactivecommons.async.kafka.config; - - -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.HandlerResolverBuilder; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; - -@Configuration -public class RCKafkaHandlersConfiguration { - - @Bean - public DomainHandlers buildHandlers(AsyncKafkaPropsDomain props, ApplicationContext context, - HandlerRegistry primaryRegistry, DefaultCommandHandler commandHandler) { - DomainHandlers handlers = new DomainHandlers(); - final Map registries = context.getBeansOfType(HandlerRegistry.class); - if (!registries.containsValue(primaryRegistry)) { - registries.put("primaryHandlerRegistry", primaryRegistry); - } - props.forEach((domain, properties) -> { - HandlerResolver resolver = HandlerResolverBuilder.buildResolver(domain, registries, commandHandler); - handlers.add(domain, resolver); - }); - return handlers; - } -} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaNotificationEventListenerConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaNotificationEventListenerConfig.java deleted file mode 100644 index eb84c021..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaNotificationEventListenerConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.reactivecommons.async.kafka.config; - -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; -import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; -import org.reactivecommons.async.kafka.listeners.ApplicationNotificationsListener; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import reactor.core.publisher.Mono; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -public class RCKafkaNotificationEventListenerConfig { - - @Bean - public ApplicationNotificationsListener kafkaNotificationEventListener(ConnectionManager manager, - DomainHandlers handlers, - AsyncKafkaPropsDomain asyncPropsDomain, - MessageConverter messageConverter, - CustomReporter customReporter) { - AsyncKafkaProps props = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - ApplicationNotificationsListener eventListener = new ApplicationNotificationsListener( - manager.getListener(DEFAULT_DOMAIN), - handlers.get(DEFAULT_DOMAIN), - messageConverter, - props.getWithDLQRetry(), - props.getCreateTopology(), - props.getMaxRetries(), - props.getRetryDelay(), - manager.getDiscardNotifier(DEFAULT_DOMAIN), - customReporter, - props.getAppName()); - - eventListener.startListener(manager.getTopologyCreator(DEFAULT_DOMAIN)); - - return eventListener; - } -} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java index 1636c90e..55919f1e 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java @@ -7,7 +7,7 @@ import lombok.Setter; import lombok.experimental.SuperBuilder; import org.reactivecommons.async.kafka.config.KafkaProperties; -import org.reactivecommons.async.starter.GenericAsyncProps; +import org.reactivecommons.async.starter.props.GenericAsyncProps; import org.springframework.boot.context.properties.NestedConfigurationProperty; @@ -43,4 +43,12 @@ public class AsyncKafkaProps extends GenericAsyncProps { @Builder.Default private Boolean checkExistingTopics = true; + @Builder.Default + private boolean useDiscardNotifierPerDomain = false; + + @Builder.Default + private boolean enabled = true; + + @Builder.Default + private String brokerType = "kafka"; } diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java index fb50627b..ea9f9496 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java @@ -3,7 +3,7 @@ import lombok.Getter; import lombok.Setter; import org.reactivecommons.async.kafka.config.KafkaProperties; -import org.reactivecommons.async.starter.GenericAsyncPropsDomain; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; import org.springframework.beans.factory.annotation.Value; import java.lang.reflect.Constructor; @@ -23,8 +23,7 @@ public AsyncKafkaPropsDomain(@Value("${spring.application.name}") String default @SuppressWarnings("unchecked") public static AsyncPropsDomainBuilder builder() { - return GenericAsyncPropsDomain.builder(AsyncKafkaProps.class, - KafkaProperties.class, + return GenericAsyncPropsDomain.builder(KafkaProperties.class, AsyncKafkaPropsDomainProperties.class, (Constructor) AsyncKafkaPropsDomain.class.getDeclaredConstructors()[0]); } diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java index 015c7cf8..186910d3 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java @@ -1,7 +1,7 @@ package org.reactivecommons.async.kafka.config.props; import org.reactivecommons.async.kafka.config.KafkaProperties; -import org.reactivecommons.async.starter.GenericAsyncPropsDomainProperties; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Map; @@ -9,7 +9,7 @@ @ConfigurationProperties(prefix = "reactive.commons.kafka") public class AsyncKafkaPropsDomainProperties extends GenericAsyncPropsDomainProperties { - public AsyncKafkaPropsDomainProperties(Map m) { + public AsyncKafkaPropsDomainProperties(Map m) { super(m); } diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java index ca677543..036432b4 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java @@ -1640,12 +1640,12 @@ public void setRandom(boolean random) { public static class Cleanup { /** - * Cleanup the application’s local state directory on startup. + * Cleanup the application?s local state directory on startup. */ private boolean onStartup = false; /** - * Cleanup the application’s local state directory on shutdown. + * Cleanup the application?s local state directory on shutdown. */ private boolean onShutdown = false; diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java new file mode 100644 index 00000000..2e2e8c30 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java @@ -0,0 +1,34 @@ +package org.reactivecommons.async.kafka.health; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.kafka.clients.admin.AdminClient; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; + +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; + +@Log4j2 +@AllArgsConstructor +public class KafkaReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + private final String domain; + private final AdminClient adminClient; + + @Override + protected Mono doHealthCheck(Health.Builder builder) { + builder.withDetail(DOMAIN, domain); + return checkKafkaHealth() + .map(clusterId -> builder.up().withDetail(VERSION, clusterId).build()) + .onErrorReturn(builder.down().build()); + } + + private Mono checkKafkaHealth() { + return Mono.fromFuture(adminClient.describeCluster().clusterId() + .toCompletionStage() + .toCompletableFuture()) + .doOnError(e -> log.error("Error checking Kafka health in domain {}", domain, e)); + } + +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/kafka/RCKafkaConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/kafka/RCKafkaConfig.java new file mode 100644 index 00000000..3b830911 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/kafka/RCKafkaConfig.java @@ -0,0 +1,54 @@ +package org.reactivecommons.async.starter.impl.kafka; + +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.kafka.KafkaBrokerProviderFactory; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.KafkaPropertiesAutoConfig; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@EnableConfigurationProperties({KafkaPropertiesAutoConfig.class, AsyncKafkaPropsDomainProperties.class}) +@Import({AsyncKafkaPropsDomain.class, KafkaBrokerProviderFactory.class}) +public class RCKafkaConfig { + + @Bean + @ConditionalOnMissingBean(KafkaCustomizations.class) + public KafkaCustomizations defaultKafkaCustomizations() { + return new KafkaCustomizations(); + } + + @Bean + @ConditionalOnMissingBean(KafkaJacksonMessageConverter.class) + public KafkaJacksonMessageConverter kafkaJacksonMessageConverter(ObjectMapperSupplier objectMapperSupplier) { + return new KafkaJacksonMessageConverter(objectMapperSupplier.get()); + } + + @Bean + @ConditionalOnMissingBean(AsyncKafkaPropsDomain.KafkaSecretFiller.class) + public AsyncKafkaPropsDomain.KafkaSecretFiller defaultKafkaSecretFiller() { + return (ignoredDomain, ignoredProps) -> { + }; + } + + @Bean + @ConditionalOnMissingBean(KafkaProperties.class) + public KafkaProperties defaultKafkaProperties(KafkaPropertiesAutoConfig properties, ObjectMapperSupplier supplier) { + return supplier.get().convertValue(properties, KafkaProperties.class); + } + + @Bean + @ConditionalOnMissingBean(SslBundles.class) + public SslBundles defaultSslBundles() { + return new DefaultSslBundleRegistry(); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java new file mode 100644 index 00000000..f0f4463a --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java @@ -0,0 +1,75 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.boot.ssl.SslBundles; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +class KafkaBrokerProviderFactoryTest { + private final ReactiveReplyRouter router = new ReactiveReplyRouter(); + @Mock + private KafkaJacksonMessageConverter converter; + @Mock + private MeterRegistry meterRegistry; + @Mock + private CustomReporter errorReporter; + @Mock + private KafkaCustomizations customizations; + @Mock + private SslBundles sslBundles; + + private BrokerProviderFactory providerFactory; + + @BeforeEach + public void setUp() { + providerFactory = new KafkaBrokerProviderFactory(router, converter, meterRegistry, errorReporter, + customizations, sslBundles); + } + + @Test + void shouldReturnBrokerType() { + // Arrange + // Act + String brokerType = providerFactory.getBrokerType(); + // Assert + assertEquals("kafka", brokerType); + } + + @Test + void shouldReturnCreateDiscardProvider() { + // Arrange + AsyncKafkaProps props = new AsyncKafkaProps(); + props.setCheckExistingTopics(false); + // Act + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Assert + assertThat(discardProvider).isInstanceOf(KafkaDiscardProvider.class); + } + + @Test + void shouldReturnBrokerProvider() { + // Arrange + AsyncKafkaProps props = new AsyncKafkaProps(); + props.setCheckExistingTopics(false); + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Act + BrokerProvider brokerProvider = providerFactory.getProvider("domain", props, discardProvider); + // Assert + assertThat(brokerProvider).isInstanceOf(KafkaBrokerProvider.class); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java new file mode 100644 index 00000000..c0f862a4 --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java @@ -0,0 +1,147 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.kafka.health.KafkaReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundles; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import javax.sound.midi.Receiver; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class KafkaBrokerProviderTest { + private final AsyncKafkaProps props = new AsyncKafkaProps(); + private final SslBundles sslBundles = new DefaultSslBundleRegistry(); + private final KafkaCustomizations customizations = new KafkaCustomizations(); + @Mock + private ReactiveMessageListener listener; + @Mock + private TopologyCreator creator; + @Mock + private HandlerResolver handlerResolver; + @Mock + private KafkaJacksonMessageConverter messageConverter; + @Mock + private CustomReporter customReporter; + @Mock + private Receiver receiver; + @Mock + private ReactiveReplyRouter router; + @Mock + private MeterRegistry meterRegistry; + @Mock + private ReactiveMessageSender sender; + @Mock + private DiscardNotifier discardNotifier; + @Mock + private KafkaReactiveHealthIndicator healthIndicator; + + + private BrokerProvider brokerProvider; + + + @BeforeEach + public void init() { + props.setAppName("test"); + brokerProvider = new KafkaBrokerProvider(DEFAULT_DOMAIN, + props, + router, + messageConverter, + meterRegistry, + customReporter, + healthIndicator, + listener, + sender, + discardNotifier, + creator, + customizations, + sslBundles); + } + + @Test + void shouldCreateDomainEventBus() { + // Act + DomainEventBus domainBus = brokerProvider.getDomainBus(); + // Assert + assertThat(domainBus).isExactlyInstanceOf(KafkaDomainEventBus.class); + } + + @Test + void shouldCreateDirectAsyncGateway() { + // Act + DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(handlerResolver); + // Assert + assertThat(domainBus).isExactlyInstanceOf(KafkaDirectAsyncGateway.class); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void shouldListenDomainEvents() { + List mockedListeners = spy(List.of()); + when(mockedListeners.isEmpty()).thenReturn(false); + when(handlerResolver.getEventListeners()).thenReturn(mockedListeners); + when(creator.createTopics(any())).thenReturn(Mono.empty()); + when(listener.getMaxConcurrency()).thenReturn(1); + when(listener.listen(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenDomainEvents(handlerResolver); + // Assert + verify(listener, times(1)).listen(any(String.class), any()); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void shouldListenNotificationEvents() { + List mockedListeners = spy(List.of()); + when(mockedListeners.isEmpty()).thenReturn(false); + when(handlerResolver.getNotificationListeners()).thenReturn(mockedListeners); + when(creator.createTopics(any())).thenReturn(Mono.empty()); + when(listener.getMaxConcurrency()).thenReturn(1); + when(listener.listen(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenNotificationEvents(handlerResolver); + // Assert + verify(listener, times(1)).listen(any(String.class), any()); + } + + @Test + void shouldProxyHealthCheck() { + when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> Health.up().build())); + // Act + Mono flow = brokerProvider.healthCheck(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().getCode().equals("UP")) + .verifyComplete(); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java new file mode 100644 index 00000000..9723e0e5 --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java @@ -0,0 +1,38 @@ +package org.reactivecommons.async.kafka; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.springframework.boot.ssl.SslBundles; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class KafkaDiscardProviderTest { + @Mock + private KafkaJacksonMessageConverter converter; + @Mock + private KafkaCustomizations customizations; + @Mock + private SslBundles sslBundles; + + @Test + void shouldCreateDiscardNotifier() { + // Arrange + AsyncKafkaProps props = new AsyncKafkaProps(); + props.setCheckExistingTopics(false); + props.setConnectionProperties(new KafkaProperties()); + KafkaDiscardProvider discardProvider = new KafkaDiscardProvider(props, converter, customizations, sslBundles); + // Act + DiscardNotifier notifier = discardProvider.get(); + // Assert + assertThat(notifier).isExactlyInstanceOf(DLQDiscardNotifier.class); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java new file mode 100644 index 00000000..0284ec4c --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java @@ -0,0 +1,71 @@ +package org.reactivecommons.async.kafka.health; + +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.DescribeClusterResult; +import org.apache.kafka.common.KafkaFuture; +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.Status; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class KafkaReactiveHealthIndicatorTest { + @Mock + private AdminClient adminClient; + @Mock + private DescribeClusterResult describeClusterResult; + + private KafkaReactiveHealthIndicator indicator; + + @BeforeEach + void setup() { + indicator = new KafkaReactiveHealthIndicator(DEFAULT_DOMAIN, adminClient); + } + + @Test + void shouldBeUp() { + // Arrange + when(adminClient.describeCluster()).thenReturn(describeClusterResult); + when(describeClusterResult.clusterId()).thenReturn(KafkaFuture.completedFuture("cluster123")); + // Act + Mono result = indicator.doHealthCheck(new Builder()); + // Assert + StepVerifier.create(result) + .assertNext(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals("cluster123", health.getDetails().get("version")); + assertEquals(Status.UP, health.getStatus()); + }) + .verifyComplete(); + } + + @Test + void shouldBeDown() { + // Arrange + when(adminClient.describeCluster()).thenReturn(describeClusterResult); + KafkaFutureImpl future = new KafkaFutureImpl<>(); + future.completeExceptionally(new RuntimeException("simulate error")); + when(describeClusterResult.clusterId()).thenReturn(future); + // Act + Mono result = indicator.doHealthCheck(new Builder()); + // Assert + StepVerifier.create(result) + .expectNextMatches(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals(Status.DOWN, health.getStatus()); + return true; + }) + .verifyComplete(); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/KafkaConfigTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/KafkaConfigTest.java new file mode 100644 index 00000000..d9fcac5b --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/KafkaConfigTest.java @@ -0,0 +1,44 @@ +package org.reactivecommons.async.starter.impl.rabbit; + + +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.kafka.KafkaBrokerProviderFactory; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.impl.kafka.RCKafkaConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = { + RCKafkaConfig.class, + AsyncKafkaPropsDomain.class, + KafkaBrokerProviderFactory.class, + ReactiveCommonsConfig.class}) +class KafkaConfigTest { + @Autowired + private KafkaJacksonMessageConverter converter; + @Autowired + private ConnectionManager manager; + + @Test + void shouldHasConverter() { + // Arrange + // Act + // Assert + assertThat(converter).isNotNull(); + } + + @Test + void shouldHasManager() { + // Arrange + // Act + // Assert + assertThat(manager).isNotNull(); + assertThat(manager.getProviders()).isNotEmpty(); + assertThat(manager.getProviders().get("app").getProps().getAppName()).isEqualTo("async-kafka-starter"); + } +} diff --git a/starters/async-kafka-starter/src/test/resources/application.yaml b/starters/async-kafka-starter/src/test/resources/application.yaml new file mode 100644 index 00000000..a5c0534e --- /dev/null +++ b/starters/async-kafka-starter/src/test/resources/application.yaml @@ -0,0 +1,8 @@ +spring: + application: + name: async-kafka-starter +reactive: + commons: + kafka: + app: + checkExistingTopics: false \ No newline at end of file diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java similarity index 77% rename from starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java index a68a2e90..2e4ac2ee 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import io.micrometer.core.instrument.MeterRegistry; import lombok.AllArgsConstructor; @@ -22,13 +22,17 @@ public class DirectAsyncGatewayConfig { private String appName; - public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, ReactiveMessageSender rSender, MessageConverter converter, - MeterRegistry meterRegistry) throws Exception { - return new RabbitDirectAsyncGateway(config, router, rSender, directMessagesExchangeName, converter, meterRegistry); + public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, + ReactiveMessageSender rSender, MessageConverter converter, + MeterRegistry meterRegistry) { + return new RabbitDirectAsyncGateway(config, router, rSender, directMessagesExchangeName, converter, + meterRegistry); } - public ApplicationReplyListener msgListener(ReactiveReplyRouter router, BrokerConfig config, ReactiveMessageListener listener, boolean createTopology) { - final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, listener, generateName(), globalReplyExchangeName, createTopology); + public ApplicationReplyListener msgListener(ReactiveReplyRouter router, BrokerConfig config, + ReactiveMessageListener listener, boolean createTopology) { + final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, listener, generateName(), + globalReplyExchangeName, createTopology); replyListener.startListening(config.getRoutingKey()); return replyListener; } @@ -50,7 +54,7 @@ public String generateName() { .putLong(uuid.getLeastSignificantBits()); // Convert to base64 and remove trailing = return this.appName + encodeToUrlSafeString(bb.array()) - .replaceAll("=", ""); + .replace("=", ""); } public static String encodeToUrlSafeString(byte[] src) { diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java similarity index 92% rename from starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java index fe01f0c3..d158d48c 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.rabbit.RabbitDomainEventBus; diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java similarity index 83% rename from starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java index 6000a897..f12d3eaf 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java @@ -1,16 +1,21 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import lombok.extern.java.Log; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import reactor.core.publisher.Mono; -import reactor.rabbitmq.*; +import reactor.rabbitmq.ChannelPool; +import reactor.rabbitmq.ChannelPoolFactory; +import reactor.rabbitmq.ChannelPoolOptions; +import reactor.rabbitmq.RabbitFlux; +import reactor.rabbitmq.Sender; +import reactor.rabbitmq.SenderOptions; import reactor.util.retry.Retry; import java.time.Duration; @@ -39,12 +44,6 @@ public ReactiveMessageSender messageSender(ConnectionFactoryProvider provider, M return new ReactiveMessageSender(sender, appName, converter, new TopologyCreator(sender)); } - /*public ReactiveMessageListener messageListener(ConnectionFactoryProvider provider) { - final Mono connection = createSenderConnectionMono(provider.getConnectionFactory(), "listener"); - Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); - return new ReactiveMessageListener(receiver, new TopologyCreator(connection)); - }*/ - public ConnectionFactoryProvider connectionFactory(RabbitProperties properties) { final ConnectionFactory factory = new ConnectionFactory(); factory.setHost(properties.getHost()); diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitProperties.java similarity index 69% rename from starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitProperties.java index 48045b68..93c9a1f3 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitProperties.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import lombok.Data; @@ -7,7 +7,7 @@ public class RabbitProperties { private String host = "localhost"; private int port = 5672; private String username = "guest"; - private String password = "guest"; + private String password = "guest"; //NOSONAR private String virtualHost; private Integer channelPoolMaxCacheSize; } diff --git a/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle index 51c58ebb..c4b4025f 100644 --- a/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle +++ b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle @@ -5,7 +5,7 @@ ext { dependencies { api project(':async-rabbit') - api project(':shared-starter') + api project(':async-commons-starter') compileOnly 'org.springframework.boot:spring-boot-starter' compileOnly 'org.springframework.boot:spring-boot-starter-actuator' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java deleted file mode 100644 index f4418ec8..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.reactivecommons.async; - -import io.micrometer.core.instrument.MeterRegistry; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.ConnectionManager; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -public class RabbitEDADirectAsyncGateway extends RabbitDirectAsyncGateway { - private final ConnectionManager manager; - - public RabbitEDADirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, ConnectionManager manager, String exchange, MessageConverter converter, MeterRegistry meterRegistry) { - super(config, router, manager.getSender(DEFAULT_DOMAIN), exchange, converter, meterRegistry); - this.manager = manager; - } - - @Override - protected ReactiveMessageSender resolveSender(String domain) { - return manager.getSender(domain); - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java deleted file mode 100644 index 6820ab50..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.EventBusConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(EventBusConfig.class) -@Configuration -public @interface EnableDomainEventBus { -} - - - diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java deleted file mode 100644 index 87791c20..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.EventListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(EventListenersConfig.class) -@Configuration -public @interface EnableEventListeners { -} - - - diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java deleted file mode 100644 index c4bf5a80..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; -import org.reactivecommons.async.rabbit.config.EventListenersConfig; -import org.reactivecommons.async.rabbit.config.NotificationListenersConfig; -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - -/** - * This annotation enables all messages listeners (Query, Commands, Events). If you want to enable separately, please use - * EnableCommandListeners, EnableQueryListeners or EnableEventListeners. - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import({CommandListenersConfig.class, QueryListenerConfig.class, EventListenersConfig.class, NotificationListenersConfig.class}) -@Configuration -public @interface EnableMessageListeners { -} - - - diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java deleted file mode 100644 index 516f479e..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.NotificationListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(NotificationListenersConfig.class) -@Configuration -public @interface EnableNotificationListener { -} - - - diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java new file mode 100644 index 00000000..5110e069 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java @@ -0,0 +1,160 @@ +package org.reactivecommons.async.rabbit; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.java.Log; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; +import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationReplyListener; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; + +import static reactor.rabbitmq.ExchangeSpecification.exchange; + +@Log +@Getter +@AllArgsConstructor +public class RabbitMQBrokerProvider implements BrokerProvider { + private final String domain; + private final AsyncProps props; + private final BrokerConfig config; + private final ReactiveReplyRouter router; + private final RabbitJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final RabbitReactiveHealthIndicator healthIndicator; + private final ReactiveMessageListener receiver; + private final ReactiveMessageSender sender; + private final DiscardNotifier discardNotifier; + + @Override + public DomainEventBus getDomainBus() { + final String exchangeName = props.getBrokerConfigProps().getDomainEventsExchangeName(); + if (props.getCreateTopology()) { + sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); + } + return new RabbitDomainEventBus(sender, exchangeName, config); + } + + @Override + public DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver) { + String exchangeName = props.getBrokerConfigProps().getDirectMessagesExchangeName(); + if (props.getCreateTopology()) { + sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("direct")).subscribe(); + } + listenReplies(resolver); + return new RabbitDirectAsyncGateway(config, router, sender, exchangeName, converter, meterRegistry); + } + + @Override + public void listenDomainEvents(HandlerResolver resolver) { + if (!props.getDomain().isIgnoreThisListener()) { + final ApplicationEventListener listener = new ApplicationEventListener(receiver, + props.getBrokerConfigProps().getEventsQueue(), + props.getBrokerConfigProps().getDomainEventsExchangeName(), + resolver, + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + props.getDomain().getEvents().getMaxLengthBytes(), + discardNotifier, + errorReporter, + props.getAppName()); + listener.startListener(); + } + } + + @Override + public void listenNotificationEvents(HandlerResolver resolver) { + if (!resolver.getNotificationListeners().isEmpty()) { + final ApplicationNotificationListener listener = new ApplicationNotificationListener( + receiver, + props.getBrokerConfigProps().getDomainEventsExchangeName(), + props.getBrokerConfigProps().getNotificationsQueue(), + props.getCreateTopology(), + resolver, + converter, + discardNotifier, + errorReporter); + listener.startListener(); + } + } + + @Override + public void listenCommands(HandlerResolver resolver) { + ApplicationCommandListener commandListener = new ApplicationCommandListener( + receiver, + props.getBrokerConfigProps().getCommandsQueue(), + resolver, + props.getDirect().getExchange(), + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getDelayedCommands(), + props.getMaxRetries(), + props.getRetryDelay(), + props.getDirect().getMaxLengthBytes(), + discardNotifier, + errorReporter); + + commandListener.startListener(); + } + + @Override + public void listenQueries(HandlerResolver resolver) { + final ApplicationQueryListener listener = new ApplicationQueryListener( + receiver, + props.getBrokerConfigProps().getQueriesQueue(), + resolver, + sender, + props.getBrokerConfigProps().getDirectMessagesExchangeName(), + converter, + props.getBrokerConfigProps().getGlobalReplyExchangeName(), + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + props.getGlobal().getMaxLengthBytes(), + props.getDirect().isDiscardTimeoutQueries(), + discardNotifier, + errorReporter); + + listener.startListener(); + } + + @Override + public void listenReplies(HandlerResolver resolver) { + if (props.isListenReplies()) { + final ApplicationReplyListener replyListener = + new ApplicationReplyListener(router, + receiver, + props.getBrokerConfigProps().getReplyQueue(), + props.getBrokerConfigProps().getGlobalReplyExchangeName(), + props.getCreateTopology()); + replyListener.startListening(config.getRoutingKey()); + } + } + + @Override + public Mono healthCheck() { + return healthIndicator.health(); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java new file mode 100644 index 00000000..8c81db82 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java @@ -0,0 +1,57 @@ +package org.reactivecommons.async.rabbit; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.AllArgsConstructor; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.stereotype.Service; + +@Service("rabbitmq") +@AllArgsConstructor +public class RabbitMQBrokerProviderFactory implements BrokerProviderFactory { + private final BrokerConfig config; + private final ReactiveReplyRouter router; + private final RabbitJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + + @Override + public String getBrokerType() { + return "rabbitmq"; + } + + @Override + public DiscardProvider getDiscardProvider(AsyncProps props) { + return new RabbitMQDiscardProvider(props, config, converter); + } + + @Override + public BrokerProvider getProvider(String domain, AsyncProps props, DiscardProvider discardProvider) { + RabbitProperties properties = props.getConnectionProperties(); + ConnectionFactoryProvider provider = RabbitMQSetupUtils.connectionFactoryProvider(properties); + RabbitReactiveHealthIndicator healthIndicator = + new RabbitReactiveHealthIndicator(domain, provider.getConnectionFactory()); + ReactiveMessageSender sender = RabbitMQSetupUtils.createMessageSender(provider, props, converter); + ReactiveMessageListener listener = RabbitMQSetupUtils.createMessageListener(provider, props); + DiscardNotifier discardNotifier; + if (props.isUseDiscardNotifierPerDomain()) { + discardNotifier = RabbitMQSetupUtils.createDiscardNotifier(sender, props, config, converter); + } else { + discardNotifier = discardProvider.get(); + } + return new RabbitMQBrokerProvider(domain, props, config, router, converter, meterRegistry, errorReporter, + healthIndicator, listener, sender, discardNotifier); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProvider.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProvider.java new file mode 100644 index 00000000..36615c4e --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProvider.java @@ -0,0 +1,34 @@ +package org.reactivecommons.async.rabbit; + +import lombok.AllArgsConstructor; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.starter.broker.DiscardProvider; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@AllArgsConstructor +public class RabbitMQDiscardProvider implements DiscardProvider { + private final AsyncProps props; + private final BrokerConfig config; + private final MessageConverter converter; + private final Map discardNotifier = new ConcurrentHashMap<>(); + + @Override + public DiscardNotifier get() { + return discardNotifier.computeIfAbsent(true, this::buildDiscardNotifier); + } + + private DiscardNotifier buildDiscardNotifier(boolean ignored) { + RabbitProperties properties = props.getConnectionProperties(); + ConnectionFactoryProvider provider = RabbitMQSetupUtils.connectionFactoryProvider(properties); + ReactiveMessageSender sender = RabbitMQSetupUtils.createMessageSender(provider, props, converter); + return RabbitMQSetupUtils.createDiscardNotifier(sender, props, config, converter); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java new file mode 100644 index 00000000..0d102085 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java @@ -0,0 +1,122 @@ +package org.reactivecommons.async.rabbit; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.extern.java.Log; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.springframework.boot.context.properties.PropertyMapper; +import reactor.core.publisher.Mono; +import reactor.rabbitmq.ChannelPool; +import reactor.rabbitmq.ChannelPoolFactory; +import reactor.rabbitmq.ChannelPoolOptions; +import reactor.rabbitmq.RabbitFlux; +import reactor.rabbitmq.Receiver; +import reactor.rabbitmq.ReceiverOptions; +import reactor.rabbitmq.Sender; +import reactor.rabbitmq.SenderOptions; +import reactor.rabbitmq.Utils; +import reactor.util.retry.Retry; + +import java.time.Duration; +import java.util.logging.Level; + +@Log +@UtilityClass +public class RabbitMQSetupUtils { + private static final String LISTENER_TYPE = "listener"; + private static final String SENDER_TYPE = "sender"; + + @SneakyThrows + public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperties properties) { + final ConnectionFactory factory = new ConnectionFactory(); + PropertyMapper map = PropertyMapper.get(); + map.from(properties::determineHost).whenNonNull().to(factory::setHost); + map.from(properties::determinePort).to(factory::setPort); + map.from(properties::determineUsername).whenNonNull().to(factory::setUsername); + map.from(properties::determinePassword).whenNonNull().to(factory::setPassword); + map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost); + factory.useNio(); + if (properties.getSsl() != null && properties.getSsl().isEnabled()) { + factory.useSslProtocol(); + } + return () -> factory; + } + + + public static ReactiveMessageSender createMessageSender(ConnectionFactoryProvider provider, + AsyncProps props, + MessageConverter converter) { + final Sender sender = RabbitFlux.createSender(reactiveCommonsSenderOptions(props.getAppName(), provider, + props.getConnectionProperties())); + return new ReactiveMessageSender(sender, props.getAppName(), converter, new TopologyCreator(sender)); + } + + public static ReactiveMessageListener createMessageListener(ConnectionFactoryProvider provider, AsyncProps props) { + final Mono connection = + createConnectionMono(provider.getConnectionFactory(), props.getAppName(), LISTENER_TYPE); + final Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); + final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); + + return new ReactiveMessageListener(receiver, + new TopologyCreator(sender), + props.getFlux().getMaxConcurrency(), + props.getPrefetchCount()); + } + + public static TopologyCreator createTopologyCreator(AsyncProps props) { + ConnectionFactoryProvider provider = connectionFactoryProvider(props.getConnectionProperties()); + final Mono connection = createConnectionMono(provider.getConnectionFactory(), + props.getAppName(), LISTENER_TYPE); + final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); + return new TopologyCreator(sender); + } + + public static DiscardNotifier createDiscardNotifier(ReactiveMessageSender sender, AsyncProps props, + BrokerConfig brokerConfig, MessageConverter converter) { + DomainEventBus appDomainEventBus = new RabbitDomainEventBus(sender, + props.getBrokerConfigProps().getDomainEventsExchangeName(), brokerConfig); + return new DLQDiscardNotifier(appDomainEventBus, converter); + } + + private static SenderOptions reactiveCommonsSenderOptions(String appName, ConnectionFactoryProvider provider, RabbitProperties rabbitProperties) { + final Mono senderConnection = createConnectionMono(provider.getConnectionFactory(), appName, SENDER_TYPE); + final ChannelPoolOptions channelPoolOptions = new ChannelPoolOptions(); + final PropertyMapper map = PropertyMapper.get(); + + map.from(rabbitProperties.getCache().getChannel()::getSize).whenNonNull() + .to(channelPoolOptions::maxCacheSize); + + final ChannelPool channelPool = ChannelPoolFactory.createChannelPool( + senderConnection, + channelPoolOptions + ); + + return new SenderOptions() + .channelPool(channelPool) + .resourceManagementChannelMono(channelPool.getChannelMono() + .transform(Utils::cache)); + } + + private static Mono createConnectionMono(ConnectionFactory factory, String connectionPrefix, String connectionType) { + return Mono.fromCallable(() -> factory.newConnection(connectionPrefix + " " + connectionType)) + .doOnError(err -> + log.log(Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err) + ) + .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) + .maxBackoff(Duration.ofMillis(3000))) + .cache(); + } + +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java deleted file mode 100644 index 0e1a5477..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class CommandListenersConfig { - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationCommandListener applicationCommandListener(ConnectionManager manager, - DomainHandlers handlers, - MessageConverter converter, - CustomReporter errorReporter) { - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - ApplicationCommandListener commandListener = new ApplicationCommandListener(manager.getListener(DEFAULT_DOMAIN), - asyncProps.getBrokerConfigProps().getCommandsQueue(), handlers.get(DEFAULT_DOMAIN), - asyncProps.getDirect().getExchange(), converter, asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), asyncProps.getDelayedCommands(), asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), asyncProps.getDirect().getMaxLengthBytes(), - manager.getDiscardNotifier(DEFAULT_DOMAIN), errorReporter); - - commandListener.startListener(); - - return commandListener; - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java deleted file mode 100644 index a2ac9760..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; - -import java.util.Map; -import java.util.TreeMap; -import java.util.function.BiConsumer; - -public class ConnectionManager { - private final Map connections = new TreeMap<>(); - - @Builder - @Getter - public static class DomainConnections { - private final ReactiveMessageListener listener; - private final ReactiveMessageSender sender; - private final ConnectionFactoryProvider provider; - @Setter - private DiscardNotifier discardNotifier; - } - - public void forSender(BiConsumer consumer) { - connections.forEach((key, conn) -> consumer.accept(key, conn.getSender())); - } - - public void forListener(BiConsumer consumer) { - connections.forEach((key, conn) -> consumer.accept(key, conn.getListener())); - } - - public void setDiscardNotifier(String domain, DiscardNotifier discardNotifier) { - getChecked(domain).setDiscardNotifier(discardNotifier); - } - - public ConnectionManager addDomain(String domain, ReactiveMessageListener listener, ReactiveMessageSender sender, - ConnectionFactoryProvider provider) { - connections.put(domain, DomainConnections.builder() - .listener(listener) - .sender(sender) - .provider(provider) - .build()); - return this; - } - - public ReactiveMessageSender getSender(String domain) { - return getChecked(domain).getSender(); - } - - public ReactiveMessageListener getListener(String domain) { - return getChecked(domain).getListener(); - } - - private DomainConnections getChecked(String domain) { - DomainConnections domainConnections = connections.get(domain); - if (domainConnections == null) { - throw new RuntimeException("You are trying to use the domain " + domain - + " but this connection is not defined"); - } - return domainConnections; - } - - public DiscardNotifier getDiscardNotifier(String domain) { - return getChecked(domain).getDiscardNotifier(); - } - - public Map getProviders() { - return connections; - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java deleted file mode 100644 index 289a7925..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.async.RabbitEDADirectAsyncGateway; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationReplyListener; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Log -@Configuration -@Import(RabbitMqConfig.class) -@RequiredArgsConstructor -public class DirectAsyncGatewayConfig { - - @Bean - public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, - ConnectionManager manager, MessageConverter converter, - MeterRegistry meterRegistry, - AsyncPropsDomain asyncPropsDomain) { - ReactiveMessageSender sender = manager.getSender(DEFAULT_DOMAIN); - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - String exchangeName = asyncProps.getBrokerConfigProps().getDirectMessagesExchangeName(); - if (asyncProps.getCreateTopology()) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("direct")).subscribe(); - } - return new RabbitEDADirectAsyncGateway(config, router, manager, exchangeName, converter, meterRegistry); - } - - @Bean - public ApplicationReplyListener msgListener(ReactiveReplyRouter router, AsyncPropsDomain asyncProps, - BrokerConfig config, ConnectionManager manager) { - AtomicReference localListener = new AtomicReference<>(); - - asyncProps.forEach((domain, props) -> { - if (props.isListenReplies()) { - final ApplicationReplyListener replyListener = - new ApplicationReplyListener(router, manager.getListener(domain), - props.getBrokerConfigProps().getReplyQueue(), - props.getBrokerConfigProps().getGlobalReplyExchangeName(), props.getCreateTopology()); - replyListener.startListening(config.getRoutingKey()); - - if (DEFAULT_DOMAIN.equals(domain)) { - localListener.set(replyListener); - } - } else { - log.log(Level.WARNING,"ApplicationReplyListener is disabled in AsyncProps or app.async." + domain - + ".listenReplies for domain " + domain); - } - }); - - return localListener.get(); - } - - @Bean - public ReactiveReplyRouter router() { - return new ReactiveReplyRouter(); - } - - @Bean - @ConditionalOnMissingBean(MeterRegistry.class) - public MeterRegistry defaultRabbitMeterRegistry() { - return new SimpleMeterRegistry(); - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java deleted file mode 100644 index aed074ce..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.async.commons.HandlerResolver; - -import java.util.Map; -import java.util.TreeMap; - -public class DomainHandlers { - private final Map handlers = new TreeMap<>(); - - public void add(String domain, HandlerResolver resolver) { - this.handlers.put(domain, resolver); - } - - public HandlerResolver get(String domain) { - HandlerResolver handlerResolver = handlers.get(domain); - if (handlerResolver == null) { - throw new RuntimeException("You are trying to use the domain " + domain - + " but this connection is not defined"); - } - return handlerResolver; - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java deleted file mode 100644 index 1a84bf42..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Configuration -@Import(RabbitMqConfig.class) -public class EventBusConfig { - - @Bean // app connection - public DomainEventBus domainEventBus(ConnectionManager manager, BrokerConfig config, - AsyncPropsDomain asyncPropsDomain) { - ReactiveMessageSender sender = manager.getSender(DEFAULT_DOMAIN); - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - final String exchangeName = asyncProps.getBrokerConfigProps().getDomainEventsExchangeName(); - if (asyncProps.getCreateTopology()) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); - } - return new RabbitDomainEventBus(sender, exchangeName, config); - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java deleted file mode 100644 index 53466827..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class EventListenersConfig { - - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationEventListener eventListener(MessageConverter messageConverter, - ConnectionManager manager, - DomainHandlers handlers, - CustomReporter errorReporter) { - AtomicReference external = new AtomicReference<>(); - manager.forListener((domain, receiver) -> { - AsyncProps asyncProps = asyncPropsDomain.getProps(domain); - if (!asyncProps.getDomain().isIgnoreThisListener()) { - final ApplicationEventListener listener = new ApplicationEventListener(receiver, - asyncProps.getBrokerConfigProps().getEventsQueue(), - asyncProps.getBrokerConfigProps().getDomainEventsExchangeName(), - handlers.get(domain), - messageConverter, asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), - asyncProps.getDomain().getEvents().getMaxLengthBytes(), - manager.getDiscardNotifier(domain), - errorReporter, - asyncProps.getAppName()); - if (DEFAULT_DOMAIN.equals(domain)) { - external.set(listener); - } - listener.startListener(); - } - }); - - return external.get(); - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java deleted file mode 100644 index e0c6b8f7..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class NotificationListenersConfig { - - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationNotificationListener eventNotificationListener(ConnectionManager manager, - DomainHandlers handlers, - MessageConverter messageConverter, - CustomReporter errorReporter) { - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - final ApplicationNotificationListener listener = new ApplicationNotificationListener( - manager.getListener(DEFAULT_DOMAIN), - asyncProps.getBrokerConfigProps().getDomainEventsExchangeName(), - asyncProps.getBrokerConfigProps().getNotificationsQueue(), - asyncProps.getCreateTopology(), - handlers.get(DEFAULT_DOMAIN), - messageConverter, - manager.getDiscardNotifier(DEFAULT_DOMAIN), - errorReporter); - listener.startListener(); - return listener; - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java deleted file mode 100644 index 709b018c..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class QueryListenerConfig { - - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationQueryListener queryListener(MessageConverter converter, - DomainHandlers handlers, - ConnectionManager manager, - CustomReporter errorReporter) { - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - final ApplicationQueryListener listener = new ApplicationQueryListener(manager.getListener(DEFAULT_DOMAIN), - asyncProps.getBrokerConfigProps().getQueriesQueue(), handlers.get(DEFAULT_DOMAIN), - manager.getSender(DEFAULT_DOMAIN), asyncProps.getBrokerConfigProps().getDirectMessagesExchangeName(), - converter, asyncProps.getBrokerConfigProps().getGlobalReplyExchangeName(), asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), asyncProps.getGlobal().getMaxLengthBytes(), - asyncProps.getDirect().isDiscardTimeoutQueries(), - manager.getDiscardNotifier(DEFAULT_DOMAIN), errorReporter); - - listener.startListener(); - - return listener; - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java deleted file mode 100644 index f89906ef..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.async.rabbit.health.DomainRabbitReactiveHealthIndicator; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@ConditionalOnClass(AbstractReactiveHealthIndicator.class) -public class RabbitHealthConfig { - - @Bean - public DomainRabbitReactiveHealthIndicator rabbitHealthIndicator(ConnectionManager manager) { - return new DomainRabbitReactiveHealthIndicator(manager); - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java deleted file mode 100644 index 5e4688d1..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ /dev/null @@ -1,253 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.java.Log; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.DefaultQueryHandler; -import org.reactivecommons.async.api.DynamicRegistry; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.DLQDiscardNotifier; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.HandlerResolverBuilder; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; -import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.DynamicRegistryImp; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.ChannelPool; -import reactor.rabbitmq.ChannelPoolFactory; -import reactor.rabbitmq.ChannelPoolOptions; -import reactor.rabbitmq.RabbitFlux; -import reactor.rabbitmq.Receiver; -import reactor.rabbitmq.ReceiverOptions; -import reactor.rabbitmq.Sender; -import reactor.rabbitmq.SenderOptions; -import reactor.rabbitmq.Utils; -import reactor.util.retry.Retry; - -import java.time.Duration; -import java.util.Map; -import java.util.logging.Level; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Log -@Configuration -@RequiredArgsConstructor -@EnableConfigurationProperties({RabbitPropertiesAutoConfig.class, AsyncRabbitPropsDomainProperties.class}) -@Import({RabbitHealthConfig.class, AsyncPropsDomain.class}) -public class RabbitMqConfig { - - private static final String LISTENER_TYPE = "listener"; - - private static final String SENDER_TYPE = "sender"; - - - @Bean - public ConnectionManager buildConnectionManager(AsyncPropsDomain props, MessageConverter converter, - BrokerConfig brokerConfig) { - ConnectionManager connectionManager = new ConnectionManager(); - props.forEach((domain, properties) -> { - ConnectionFactoryProvider provider = createConnectionFactoryProvider(properties.getConnectionProperties()); - ReactiveMessageSender sender = createMessageSender(provider, properties, converter); - ReactiveMessageListener listener = createMessageListener(provider, properties); - connectionManager.addDomain(domain, listener, sender, provider); - - ReactiveMessageSender appDomainSender = connectionManager.getSender(domain); - DomainEventBus appDomainEventBus = new RabbitDomainEventBus(appDomainSender, props.getProps(domain) - .getBrokerConfigProps().getDomainEventsExchangeName(), brokerConfig); - DiscardNotifier notifier = new DLQDiscardNotifier(appDomainEventBus, converter); - connectionManager.setDiscardNotifier(domain, notifier); - }); - return connectionManager; - } - - @Bean - public DomainHandlers buildHandlers(AsyncPropsDomain props, ApplicationContext context, - HandlerRegistry primaryRegistry, DefaultCommandHandler commandHandler) { - DomainHandlers handlers = new DomainHandlers(); - final Map registries = context.getBeansOfType(HandlerRegistry.class); - if (!registries.containsValue(primaryRegistry)) { - registries.put("primaryHandlerRegistry", primaryRegistry); - } - props.forEach((domain, properties) -> { - HandlerResolver resolver = HandlerResolverBuilder.buildResolver(domain, registries, commandHandler); - handlers.add(domain, resolver); - }); - return handlers; - } - - - private ReactiveMessageSender createMessageSender(ConnectionFactoryProvider provider, - AsyncProps props, - MessageConverter converter) { - final Sender sender = RabbitFlux.createSender(reactiveCommonsSenderOptions(props.getAppName(), provider, - props.getConnectionProperties())); - return new ReactiveMessageSender(sender, props.getAppName(), converter, new TopologyCreator(sender)); - } - - private SenderOptions reactiveCommonsSenderOptions(String appName, ConnectionFactoryProvider provider, RabbitProperties rabbitProperties) { - final Mono senderConnection = createConnectionMono(provider.getConnectionFactory(), appName, SENDER_TYPE); - final ChannelPoolOptions channelPoolOptions = new ChannelPoolOptions(); - final PropertyMapper map = PropertyMapper.get(); - - map.from(rabbitProperties.getCache().getChannel()::getSize).whenNonNull() - .to(channelPoolOptions::maxCacheSize); - - final ChannelPool channelPool = ChannelPoolFactory.createChannelPool( - senderConnection, - channelPoolOptions - ); - - return new SenderOptions() - .channelPool(channelPool) - .resourceManagementChannelMono(channelPool.getChannelMono() - .transform(Utils::cache)); - } - - public ReactiveMessageListener createMessageListener(ConnectionFactoryProvider provider, AsyncProps props) { - final Mono connection = - createConnectionMono(provider.getConnectionFactory(), props.getAppName(), LISTENER_TYPE); - final Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); - final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); - - return new ReactiveMessageListener(receiver, - new TopologyCreator(sender), - props.getFlux().getMaxConcurrency(), - props.getPrefetchCount()); - } - - @SneakyThrows - public ConnectionFactoryProvider createConnectionFactoryProvider(RabbitProperties properties) { - final ConnectionFactory factory = new ConnectionFactory(); - PropertyMapper map = PropertyMapper.get(); - map.from(properties::determineHost).whenNonNull().to(factory::setHost); - map.from(properties::determinePort).to(factory::setPort); - map.from(properties::determineUsername).whenNonNull().to(factory::setUsername); - map.from(properties::determinePassword).whenNonNull().to(factory::setPassword); - map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost); - factory.useNio(); - if (properties.getSsl() != null && properties.getSsl().isEnabled()) { - factory.useSslProtocol(); - } - return () -> factory; - } - - @Bean - @ConditionalOnMissingBean - public BrokerConfig brokerConfig() { - return new BrokerConfig(); - } - - @Bean - @ConditionalOnMissingBean - public ObjectMapperSupplier objectMapperSupplier() { - return new DefaultObjectMapperSupplier(); - } - - @Bean - @ConditionalOnMissingBean - public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); - } - - @Bean - @ConditionalOnMissingBean - public CustomReporter reactiveCommonsCustomErrorReporter() { - return new CustomReporter() { - @Override - public Mono reportError(Throwable ex, Message rawMessage, Command message, boolean redelivered) { - return Mono.empty(); - } - - @Override - public Mono reportError(Throwable ex, Message rawMessage, DomainEvent message, boolean redelivered) { - return Mono.empty(); - } - - @Override - public Mono reportError(Throwable ex, Message rawMessage, AsyncQuery message, boolean redelivered) { - return Mono.empty(); - } - }; - } - - Mono createConnectionMono(ConnectionFactory factory, String connectionPrefix, String connectionType) { - return Mono.fromCallable(() -> factory.newConnection(connectionPrefix + " " + connectionType)) - .doOnError(err -> - log.log(Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err) - ) - .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) - .maxBackoff(Duration.ofMillis(3000))) - .cache(); - } - - @Bean - public DynamicRegistry dynamicRegistry(ConnectionManager connectionManager, AsyncPropsDomain asyncPropsDomain, - DomainHandlers handlers) { - IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(asyncPropsDomain.getProps(DEFAULT_DOMAIN)); - return new DynamicRegistryImp(handlers.get(DEFAULT_DOMAIN), - connectionManager.getListener(DEFAULT_DOMAIN).getTopologyCreator(), brokerConfigProps); - } - - @Bean - @ConditionalOnMissingBean - public DefaultQueryHandler defaultHandler() { - return (DefaultQueryHandler) command -> - Mono.error(new RuntimeException("No Handler Registered")); - } - - @Bean - @ConditionalOnMissingBean - public DefaultCommandHandler defaultCommandHandler() { - return message -> Mono.error(new RuntimeException("No Handler Registered")); - } - - @Bean - @ConditionalOnMissingBean(HandlerRegistry.class) - public HandlerRegistry defaultHandlerRegistry() { - return HandlerRegistry.register(); - } - - @Bean - @ConditionalOnMissingBean(AsyncPropsDomain.RabbitSecretFiller.class) - public AsyncPropsDomain.RabbitSecretFiller defaultRabbitSecretFiller() { - return (ignoredDomain, ignoredProps) -> { - }; - } - - @Bean - @ConditionalOnMissingBean(RabbitProperties.class) - public RabbitProperties defaultRabbitProperties(RabbitPropertiesAutoConfig properties, ObjectMapperSupplier supplier) { - return supplier.get().convertValue(properties, RabbitProperties.class); - } - -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java index 6c5991cb..8706565a 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java @@ -1,4 +1,6 @@ package org.reactivecommons.async.rabbit.config; +import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase; + public class RabbitProperties extends RabbitPropertiesBase { } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java index 4742df2f..e05cd797 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java @@ -1,6 +1,8 @@ package org.reactivecommons.async.rabbit.config; +import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Primary; @ConfigurationProperties(prefix = "spring.rabbitmq") public class RabbitPropertiesAutoConfig extends RabbitPropertiesBase { diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java index 4e7f0bc3..3ed6a8b7 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java @@ -8,7 +8,7 @@ import lombok.experimental.SuperBuilder; import org.reactivecommons.async.commons.config.IBrokerConfigProps; import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.starter.GenericAsyncProps; +import org.reactivecommons.async.starter.props.GenericAsyncProps; import org.springframework.boot.context.properties.NestedConfigurationProperty; @@ -63,4 +63,13 @@ public class AsyncProps extends GenericAsyncProps { @Builder.Default private Boolean createTopology = true; // auto delete queues will always be created and bound + @Builder.Default + private boolean useDiscardNotifierPerDomain = false; + + @Builder.Default + private boolean enabled = true; + + @Builder.Default + private String brokerType = "rabbitmq"; + } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java index 115f7030..d72aaff8 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java @@ -3,7 +3,7 @@ import lombok.Getter; import lombok.Setter; import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.starter.GenericAsyncPropsDomain; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; import org.springframework.beans.factory.annotation.Value; import java.lang.reflect.Constructor; @@ -22,8 +22,7 @@ public AsyncPropsDomain(@Value("${spring.application.name}") String defaultAppNa @SuppressWarnings("unchecked") public static AsyncPropsDomainBuilder builder() { - return GenericAsyncPropsDomain.builder(AsyncProps.class, - RabbitProperties.class, + return GenericAsyncPropsDomain.builder(RabbitProperties.class, AsyncRabbitPropsDomainProperties.class, (Constructor) AsyncPropsDomain.class.getDeclaredConstructors()[0]); } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java index 77f05995..b852a0db 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java @@ -1,7 +1,7 @@ package org.reactivecommons.async.rabbit.config.props; import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.starter.GenericAsyncPropsDomainProperties; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Map; @@ -13,7 +13,7 @@ public class AsyncRabbitPropsDomainProperties extends GenericAsyncPropsDomainPro public AsyncRabbitPropsDomainProperties() { } - public AsyncRabbitPropsDomainProperties(Map m) { + public AsyncRabbitPropsDomainProperties(Map m) { super(m); } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesBase.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java similarity index 98% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesBase.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java index 4849945b..41346510 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesBase.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.config.spring; import org.springframework.boot.convert.DurationUnit; import org.springframework.util.CollectionUtils; @@ -29,7 +29,7 @@ public class RabbitPropertiesBase { /** * Login to authenticate against the communications. */ - private String password = "guest"; + private String password = "guest"; //NOSONAR /** * SSL configuration. @@ -156,11 +156,11 @@ public void setAddresses(String addresses) { } private List

parseAddresses(String addresses) { - List
parsedAddresses = new ArrayList<>(); + List
parsedAddressesLocal = new ArrayList<>(); for (String address : StringUtils.commaDelimitedListToStringArray(addresses)) { - parsedAddresses.add(new Address(address)); + parsedAddressesLocal.add(new Address(address)); } - return parsedAddresses; + return parsedAddressesLocal; } public String getUsername() { diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java deleted file mode 100644 index 45794c64..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.SneakyThrows; -import lombok.extern.log4j.Log4j2; -import org.reactivecommons.async.rabbit.config.ConnectionManager; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import reactor.core.publisher.Mono; - -import java.net.SocketException; -import java.util.Map; -import java.util.stream.Collectors; - -@Log4j2 -public class DomainRabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - private static final String VERSION = "version"; - private final Map domainProviders; - - public DomainRabbitReactiveHealthIndicator(ConnectionManager manager) { - this.domainProviders = manager.getProviders().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> { - ConnectionFactory connection = entry.getValue().getProvider().getConnectionFactory().clone(); - connection.useBlockingIo(); - return connection; - })); - } - - @Override - protected Mono doHealthCheck(Health.Builder builder) { - return Mono.zip(domainProviders.entrySet().stream() - .map(entry -> checkSingle(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()), this::merge); - } - - private Health merge(Object[] results) { - Health.Builder builder = Health.up(); - for (Object obj : results) { - Status status = (Status) obj; - builder.withDetail(status.getDomain(), status.getVersion()); - } - return builder.build(); - } - - private Mono checkSingle(String domain, ConnectionFactory connectionFactory) { - return Mono.defer(() -> Mono.just(getRawVersion(connectionFactory))) - .map(version -> Status.builder().version(version).domain(domain).build()); - } - - @SneakyThrows - private String getRawVersion(ConnectionFactory factory) { - Connection connection = null; - try { - connection = factory.newConnection(); - return connection.getServerProperties().get(VERSION).toString(); - } catch (SocketException e) { - log.warn("Identified error", e); - throw new RuntimeException(e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (Exception e) { - log.error("Error closing health connection", e); - } - } - } - } -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java new file mode 100644 index 00000000..a0255f4a --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.rabbit.health; + +public class RabbitMQHealthException extends RuntimeException { + public RabbitMQHealthException(Throwable throwable) { + super(throwable); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java new file mode 100644 index 00000000..d8d0178e --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java @@ -0,0 +1,53 @@ +package org.reactivecommons.async.rabbit.health; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; + +import java.net.SocketException; + +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; + +@Log4j2 +public class RabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + private final String domain; + private final ConnectionFactory connectionFactory; + + public RabbitReactiveHealthIndicator(String domain, ConnectionFactory connectionFactory) { + this.domain = domain; + this.connectionFactory = connectionFactory.clone(); + this.connectionFactory.useBlockingIo(); + } + + @Override + protected Mono doHealthCheck(Health.Builder builder) { + builder.withDetail(DOMAIN, domain); + return Mono.fromCallable(() -> getRawVersion(connectionFactory)) + .map(status -> builder.up().withDetail(VERSION, status).build()); + } + + @SneakyThrows + private String getRawVersion(ConnectionFactory factory) { + Connection connection = null; + try { + connection = factory.newConnection(); + return connection.getServerProperties().get(VERSION).toString(); + } catch (SocketException e) { + log.warn("Identified error", e); + throw new RabbitMQHealthException(e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (Exception e) { + log.error("Error closing health connection", e); + } + } + } + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/Status.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/Status.java deleted file mode 100644 index b3be58a2..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/Status.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class Status { - private final String version; - private final String domain; -} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfig.java new file mode 100644 index 00000000..50526cb3 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfig.java @@ -0,0 +1,63 @@ +package org.reactivecommons.async.starter.impl.rabbit; + +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DynamicRegistry; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.rabbit.DynamicRegistryImp; +import org.reactivecommons.async.rabbit.RabbitMQBrokerProviderFactory; +import org.reactivecommons.async.rabbit.RabbitMQSetupUtils; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.RabbitPropertiesAutoConfig; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@Log +@Configuration +@RequiredArgsConstructor +@EnableConfigurationProperties({RabbitPropertiesAutoConfig.class, AsyncRabbitPropsDomainProperties.class}) +@Import({AsyncPropsDomain.class, RabbitMQBrokerProviderFactory.class}) +public class RabbitMQConfig { + + @Bean + @ConditionalOnMissingBean(RabbitJacksonMessageConverter.class) + public RabbitJacksonMessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { + return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); + } + + @Bean + @ConditionalOnMissingBean(DynamicRegistry.class) + public DynamicRegistry dynamicRegistry(AsyncPropsDomain asyncPropsDomain, DomainHandlers handlers) { + AsyncProps props = asyncPropsDomain.getProps(DEFAULT_DOMAIN); + TopologyCreator topologyCreator = RabbitMQSetupUtils.createTopologyCreator(props); + IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(asyncPropsDomain.getProps(DEFAULT_DOMAIN)); + return new DynamicRegistryImp(handlers.get(DEFAULT_DOMAIN), topologyCreator, brokerConfigProps); + } + + @Bean + @ConditionalOnMissingBean(AsyncPropsDomain.RabbitSecretFiller.class) + public AsyncPropsDomain.RabbitSecretFiller defaultRabbitSecretFiller() { + return (ignoredDomain, ignoredProps) -> { + }; + } + + @Bean + @ConditionalOnMissingBean(RabbitProperties.class) + public RabbitProperties defaultRabbitProperties(RabbitPropertiesAutoConfig properties, ObjectMapperSupplier supplier) { + return supplier.get().convertValue(properties, RabbitProperties.class); + } + +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java new file mode 100644 index 00000000..534db7cf --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java @@ -0,0 +1,74 @@ +package org.reactivecommons.async.rabbit; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +class RabbitMQBrokerProviderFactoryTest { + private final BrokerConfig config = new BrokerConfig(); + private final ReactiveReplyRouter router = new ReactiveReplyRouter(); + @Mock + private RabbitJacksonMessageConverter converter; + @Mock + private MeterRegistry meterRegistry; + @Mock + private CustomReporter errorReporter; + + private BrokerProviderFactory providerFactory; + + @BeforeEach + public void setUp() { + providerFactory = new RabbitMQBrokerProviderFactory(config, router, converter, meterRegistry, errorReporter); + } + + @Test + void shouldReturnBrokerType() { + // Arrange + // Act + String brokerType = providerFactory.getBrokerType(); + // Assert + assertEquals("rabbitmq", brokerType); + } + + @Test + void shouldReturnCreateDiscardProvider() { + // Arrange + AsyncProps props = new AsyncProps(); + // Act + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Assert + assertThat(discardProvider).isInstanceOf(RabbitMQDiscardProvider.class); + } + + @Test + void shouldReturnBrokerProvider() { + // Arrange + AsyncProps props = new AsyncProps(); + props.setConnectionProperties(new RabbitProperties()); + IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(props); + props.setBrokerConfigProps(brokerConfigProps); + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Act + BrokerProvider brokerProvider = providerFactory.getProvider("domain", props, discardProvider); + // Assert + assertThat(brokerProvider).isInstanceOf(RabbitMQBrokerProvider.class); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java new file mode 100644 index 00000000..5fddb65e --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java @@ -0,0 +1,192 @@ +package org.reactivecommons.async.rabbit; + +import com.rabbitmq.client.AMQP; +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.rabbitmq.BindingSpecification; +import reactor.rabbitmq.ExchangeSpecification; +import reactor.rabbitmq.QueueSpecification; +import reactor.rabbitmq.Receiver; +import reactor.test.StepVerifier; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class RabbitMQBrokerProviderTest { + private final AsyncProps props = new AsyncProps(); + private final BrokerConfig brokerConfig = new BrokerConfig(); + @Mock + private ReactiveMessageListener listener; + @Mock + private TopologyCreator creator; + @Mock + private HandlerResolver handlerResolver; + @Mock + private RabbitJacksonMessageConverter messageConverter; + @Mock + private CustomReporter customReporter; + @Mock + private Receiver receiver; + @Mock + private ReactiveReplyRouter router; + @Mock + private MeterRegistry meterRegistry; + @Mock + private ReactiveMessageSender sender; + @Mock + private DiscardNotifier discardNotifier; + @Mock + private RabbitReactiveHealthIndicator healthIndicator; + + + private BrokerProvider brokerProvider; + + + @BeforeEach + public void init() { + IBrokerConfigProps configProps = new BrokerConfigProps(props); + props.setBrokerConfigProps(configProps); + props.setAppName("test"); + brokerProvider = new RabbitMQBrokerProvider(DEFAULT_DOMAIN, + props, + brokerConfig, + router, + messageConverter, + meterRegistry, + customReporter, + healthIndicator, + listener, + sender, + discardNotifier); + } + + @Test + void shouldCreateDomainEventBus() { + when(sender.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + // Act + DomainEventBus domainBus = brokerProvider.getDomainBus(); + // Assert + assertThat(domainBus).isExactlyInstanceOf(RabbitDomainEventBus.class); + } + + @Test + void shouldCreateDirectAsyncGateway() { + when(sender.getTopologyCreator()).thenReturn(creator); + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); + when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(receiver.consumeAutoAck(any(String.class))).thenReturn(Flux.never()); + // Act + DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(handlerResolver); + // Assert + assertThat(domainBus).isExactlyInstanceOf(RabbitDirectAsyncGateway.class); + } + + @Test + void shouldListenDomainEvents() { + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenDomainEvents(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void shouldListenNotificationEvents() { + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + List mockedListeners = spy(List.of()); + when(mockedListeners.isEmpty()).thenReturn(false); + when(handlerResolver.getNotificationListeners()).thenReturn(mockedListeners); + // Act + brokerProvider.listenNotificationEvents(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + void shouldListenCommands() { + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenCommands(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + void shouldListenQueries() { + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenQueries(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + void shouldProxyHealthCheck() { + when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> Health.up().build())); + // Act + Mono flow = brokerProvider.healthCheck(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().getCode().equals("UP")) + .verifyComplete(); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderTest.java new file mode 100644 index 00000000..5243b48a --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderTest.java @@ -0,0 +1,37 @@ +package org.reactivecommons.async.rabbit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class RabbitMQDiscardProviderTest { + @Mock + private RabbitJacksonMessageConverter converter; + + @Test + void shouldCreateDiscardNotifier() { + // Arrange + AsyncProps props = new AsyncProps(); + props.setConnectionProperties(new RabbitProperties()); + IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(props); + props.setBrokerConfigProps(brokerConfigProps); + BrokerConfig brokerConfig = new BrokerConfig(); + RabbitMQDiscardProvider discardProvider = new RabbitMQDiscardProvider(props, brokerConfig, converter); + // Act + DiscardNotifier notifier = discardProvider.get(); + // Assert + assertThat(notifier).isExactlyInstanceOf(DLQDiscardNotifier.class); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java deleted file mode 100644 index cd9169ea..00000000 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.Receiver; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@ExtendWith(MockitoExtension.class) -class CommandListenersConfigTest { - - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final CommandListenersConfig config = new CommandListenersConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ConnectionManager manager = new ConnectionManager(); - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - manager.addDomain(DEFAULT_DOMAIN, listener, null, null); - handlers.add(DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void applicationCommandListener() { - final ApplicationCommandListener commandListener = config.applicationCommandListener(manager, handlers, messageConverter, customReporter); - Assertions.assertThat(commandListener).isNotNull(); - } -} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java deleted file mode 100644 index 82494ee8..00000000 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -class EventListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final EventListenersConfig config = new EventListenersConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final ReactiveMessageSender sender = mock(ReactiveMessageSender.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private ConnectionManager connectionManager; - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - connectionManager = new ConnectionManager(); - connectionManager.addDomain(HandlerRegistry.DEFAULT_DOMAIN, listener, sender, null); - handlers.add(HandlerRegistry.DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void eventListener() { - final ApplicationEventListener eventListener = config.eventListener( - messageConverter, - connectionManager, - handlers, - customReporter - ); - - Assertions.assertThat(eventListener).isNotNull(); - } -} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java deleted file mode 100644 index 30f67fca..00000000 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -class NotificationListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final NotificationListenersConfig config = new NotificationListenersConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ConnectionManager manager = new ConnectionManager(); - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - manager.addDomain(DEFAULT_DOMAIN, listener, null, null); - handlers.add(DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void eventNotificationListener() { - final ApplicationNotificationListener applicationEventListener = - config.eventNotificationListener(manager, handlers, messageConverter, customReporter); - Assertions.assertThat(applicationEventListener).isNotNull(); - } -} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java deleted file mode 100644 index 143f9a32..00000000 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.HandlerResolver; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -class QueryListenerConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final QueryListenerConfig config = new QueryListenerConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ReactiveMessageSender sender = mock(ReactiveMessageSender.class); - private final ConnectionManager manager = new ConnectionManager(); - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - manager.addDomain(DEFAULT_DOMAIN, listener, sender, null); - handlers.add(DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void queryListener() { - final ApplicationQueryListener queryListener = config.queryListener(messageConverter, handlers, manager, customReporter); - Assertions.assertThat(queryListener).isNotNull(); - } -} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java deleted file mode 100644 index 450d2f2a..00000000 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.Test; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.ext.CustomReporter; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class RabbitMqConfigTest { - - RabbitMqConfig config = new RabbitMqConfig(); - - @Test - void retryInitialConnection() throws IOException, TimeoutException { - final String connectionType = "sender"; - final String appName = "appName"; - final String connectionName = "appName sender"; - - final AtomicInteger count = new AtomicInteger(); - final Connection connection = mock(Connection.class); - ConnectionFactory factory = mock(ConnectionFactory.class); - when(factory.newConnection(connectionName)).thenAnswer(invocation -> { - if(count.incrementAndGet() == 10){ - return connection; - } - throw new RuntimeException(); - }); - StepVerifier.withVirtualTime(() -> config.createConnectionMono(factory, appName, connectionType)) - .thenAwait(Duration.ofMinutes(2)) - .expectNext(connection).verifyComplete(); - } - - @Test - void shouldCreateDefaultErrorReporter() { - final CustomReporter errorReporter = config.reactiveCommonsCustomErrorReporter(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(Command.class), true)).isNotNull(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(DomainEvent.class), true)).isNotNull(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(AsyncQuery.class), true)).isNotNull(); - } - - @Test - void shouldGenerateDefaultReeporter() { - final CustomReporter customReporter = config.reactiveCommonsCustomErrorReporter(); - final Mono r1 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(Command.class), true); - final Mono r2 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(DomainEvent.class), true); - final Mono r3 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(AsyncQuery.class), true); - - assertThat(r1).isNotNull(); - assertThat(r2).isNotNull(); - assertThat(r3).isNotNull(); - - } -} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java similarity index 58% rename from starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java index cff1e660..e2f2cc7e 100644 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java @@ -7,8 +7,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; -import org.reactivecommons.async.rabbit.config.ConnectionManager; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.boot.actuate.health.Status; @@ -16,35 +14,29 @@ import reactor.test.StepVerifier; import java.io.IOException; +import java.net.SocketException; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.TimeoutException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @ExtendWith(MockitoExtension.class) -public class DomainRabbitReactiveHealthIndicatorTest { - @Mock - private ConnectionFactoryProvider provider; +class RabbitReactiveHealthIndicatorTest { @Mock private ConnectionFactory factory; @Mock private Connection connection; - private DomainRabbitReactiveHealthIndicator indicator; + private RabbitReactiveHealthIndicator indicator; @BeforeEach void setup() { - when(provider.getConnectionFactory()).thenReturn(factory); when(factory.clone()).thenReturn(factory); - - ConnectionManager connectionManager = new ConnectionManager(); - connectionManager.addDomain(DEFAULT_DOMAIN, null, null, provider); - connectionManager.addDomain("domain2", null, null, provider); - connectionManager.addDomain("domain3", null, null, provider); - indicator = new DomainRabbitReactiveHealthIndicator(connectionManager); + indicator = new RabbitReactiveHealthIndicator(DEFAULT_DOMAIN, factory); } @Test @@ -59,9 +51,28 @@ void shouldBeUp() throws IOException, TimeoutException { // Assert StepVerifier.create(result) .assertNext(health -> { - assertEquals("1.2.3", health.getDetails().get(DEFAULT_DOMAIN)); - assertEquals("1.2.3", health.getDetails().get("domain2")); - assertEquals("1.2.3", health.getDetails().get("domain3")); + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals("1.2.3", health.getDetails().get("version")); + assertEquals(Status.UP, health.getStatus()); + }) + .verifyComplete(); + } + + @Test + void shouldBeUpAndIgnoreCloseError() throws IOException, TimeoutException { + // Arrange + Map properties = new TreeMap<>(); + properties.put("version", "1.2.3"); + when(factory.newConnection()).thenReturn(connection); + when(connection.getServerProperties()).thenReturn(properties); + doThrow(new IOException("Error closing connection")).when(connection).close(); + // Act + Mono result = indicator.doHealthCheck(new Builder()); + // Assert + StepVerifier.create(result) + .assertNext(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals("1.2.3", health.getDetails().get("version")); assertEquals(Status.UP, health.getStatus()); }) .verifyComplete(); @@ -78,4 +89,16 @@ void shouldBeDown() throws IOException, TimeoutException { .expectError(TimeoutException.class) .verify(); } + + @Test + void shouldBeDownWhenSocketException() throws IOException, TimeoutException { + // Arrange + when(factory.newConnection()).thenThrow(new SocketException("Connection timeout")); + // Act + Mono result = indicator.doHealthCheck(new Builder()); + // Assert + StepVerifier.create(result) + .expectError(RuntimeException.class) + .verify(); + } } diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfigTest.java new file mode 100644 index 00000000..240fe4b7 --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfigTest.java @@ -0,0 +1,42 @@ +package org.reactivecommons.async.starter.impl.rabbit; + +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.rabbit.RabbitMQBrokerProviderFactory; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = { + RabbitMQConfig.class, + AsyncPropsDomain.class, + RabbitMQBrokerProviderFactory.class, + ReactiveCommonsConfig.class}) +class RabbitMQConfigTest { + @Autowired + private RabbitJacksonMessageConverter converter; + @Autowired + private ConnectionManager manager; + + @Test + void shouldHasConverter() { + // Arrange + // Act + // Assert + assertThat(converter).isNotNull(); + } + + @Test + void shouldHasManager() { + // Arrange + // Act + // Assert + assertThat(manager).isNotNull(); + assertThat(manager.getProviders()).isNotEmpty(); + assertThat(manager.getProviders().get("app").getProps().getAppName()).isEqualTo("test-app"); + } +} diff --git a/starters/shared/shared-starter.gradle b/starters/shared/shared-starter.gradle deleted file mode 100644 index c135d06e..00000000 --- a/starters/shared/shared-starter.gradle +++ /dev/null @@ -1,10 +0,0 @@ -ext { - artifactId = 'shared-starter' - artifactDescription = 'Shared Starter' -} - -dependencies { - compileOnly project(':async-commons-api') - implementation 'com.fasterxml.jackson.core:jackson-databind' - implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' -} \ No newline at end of file