From 54e19b740efa761071d4a9cd7e011ba609cf3f50 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Wed, 17 Jul 2024 20:06:03 +0000 Subject: [PATCH 01/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4f054e..c317107f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v4.1.3](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.3) (2024-07-17) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.2...v4.1.3) + +**Merged pull requests:** + +- fix\(notifier\): Set the discard notifier with domain connection properties [\#113](https://github.com/reactive-commons/reactive-commons-java/pull/113) ([jhonatan-kmt](https://github.com/jhonatan-kmt)) + ## [v4.1.2](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.2) (2024-07-09) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.1...v4.1.2) diff --git a/gradle.properties b/gradle.properties index b7d0d356..50327897 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=4.1.2 +version=4.1.3 toPublish=async-commons,async-commons-api,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-commons-rabbit-starter-eda,domain-events-api,async-rabbit onlyUpdater=true \ No newline at end of file From b0adda4bbbba61457d29d3f4a3645af9d7c18589 Mon Sep 17 00:00:00 2001 From: Jhonatan Hidalgo <108900469+jhonatan-kmt@users.noreply.github.com> Date: Wed, 24 Jul 2024 22:24:50 -0500 Subject: [PATCH 02/50] fix(health): Use bloking IO for health indicator connection (#114) --- .../DomainRabbitReactiveHealthIndicator.java | 31 +++++++++---------- ...mainRabbitReactiveHealthIndicatorTest.java | 4 ++- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java index e27f87ab..45794c64 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java +++ b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java @@ -2,30 +2,35 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; 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 -@RequiredArgsConstructor public class DomainRabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { private static final String VERSION = "version"; - private final ConnectionManager manager; + 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(manager.getProviders() - .entrySet() - .stream() - .map(entry -> checkSingle(entry.getKey(), entry.getValue().getProvider())) + return Mono.zip(domainProviders.entrySet().stream() + .map(entry -> checkSingle(entry.getKey(), entry.getValue())) .collect(Collectors.toList()), this::merge); } @@ -38,17 +43,11 @@ private Health merge(Object[] results) { return builder.build(); } - private Mono checkSingle(String domain, ConnectionFactoryProvider provider) { - return Mono.defer(() -> getVersion(provider)) + private Mono checkSingle(String domain, ConnectionFactory connectionFactory) { + return Mono.defer(() -> Mono.just(getRawVersion(connectionFactory))) .map(version -> Status.builder().version(version).domain(domain).build()); } - private Mono getVersion(ConnectionFactoryProvider provider) { - return Mono.just(provider) - .map(ConnectionFactoryProvider::getConnectionFactory) - .map(this::getRawVersion); - } - @SneakyThrows private String getRawVersion(ConnectionFactory factory) { Connection connection = null; diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java index fb533280..cff1e660 100644 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java +++ b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java @@ -37,12 +37,14 @@ public class DomainRabbitReactiveHealthIndicatorTest { @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); - when(provider.getConnectionFactory()).thenReturn(factory); } @Test From 7aa3dbda995b68c632f5ba19d2dd32e880eb680d Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Thu, 25 Jul 2024 03:32:19 +0000 Subject: [PATCH 03/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c317107f..71efa72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v4.1.4](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.4) (2024-07-25) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.3...v4.1.4) + +**Merged pull requests:** + +- fix\(health\): Use blocking IO for health indicator connection [\#114](https://github.com/reactive-commons/reactive-commons-java/pull/114) ([jhonatan-kmt](https://github.com/jhonatan-kmt)) + ## [v4.1.3](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.3) (2024-07-17) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.2...v4.1.3) diff --git a/gradle.properties b/gradle.properties index 50327897..689437dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=4.1.3 +version=4.1.4 toPublish=async-commons,async-commons-api,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-commons-rabbit-starter-eda,domain-events-api,async-rabbit onlyUpdater=true \ No newline at end of file From 861e9ed8848d08f48bc16b2dfd70fd157b66c83c Mon Sep 17 00:00:00 2001 From: jcanacon <158337686+jcanacon@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:47:55 -0500 Subject: [PATCH 04/50] chore(next): add native support for Cloud Events (#115) * Add CloudEvent specification to commands and events listeners and gateways * Exclude cloud events --------- Co-authored-by: Angel David Santa Giraldo Co-authored-by: jcanacon Co-authored-by: Juan C Galvis <8420868+juancgalvis@users.noreply.github.com> --- .../test/DynamicRegistryTest.java | 3 +- .../test/SimpleDirectCommunicationTest.java | 3 +- .../test/SimpleEventNotificationTest.java | 3 +- .../perf/BlockingCommandHandlePerfTest.java | 3 +- ...allelOnBlockingInSubscriptionTimeTest.java | 3 +- .../perf/SimpleCommandHandlePerfTest.java | 3 +- .../async/api/DefaultCommandHandler.java | 4 +- .../async/api/DirectAsyncGateway.java | 4 + .../async/api/DynamicRegistry.java | 3 +- .../async/api/HandlerRegistry.java | 112 ++++++---- .../api/handlers/CloudCommandHandler.java | 6 + .../async/api/handlers/CloudEventHandler.java | 6 + .../async/api/handlers/CommandHandler.java | 4 +- .../api/handlers/DomainCommandHandler.java | 6 + .../api/handlers/DomainEventHandler.java | 6 + .../async/api/handlers/EventHandler.java | 4 +- .../registered/RegisteredCommandHandler.java | 4 +- .../registered/RegisteredEventListener.java | 4 +- .../async/api/HandlerRegistryTest.java | 157 ++++++++++---- async/async-commons/async-commons.gradle | 4 +- .../async/commons/CommandExecutor.java | 13 +- .../async/commons/DLQDiscardNotifier.java} | 63 +++--- .../async/commons/EventExecutor.java | 13 +- .../async/commons}/HandlerResolver.java | 43 ++-- .../async/commons/communications/Message.java | 17 +- .../commons/converters/MessageConverter.java | 4 + .../converters/json/CloudEventBuilderExt.java | 2 +- .../json/DefaultObjectMapperSupplier.java | 4 +- .../json/JacksonMessageConverter.java | 42 ++-- .../commons/ext/DefaultCustomReporter.java | 25 +++ .../utils/resolver/HandlerResolverUtil.java | 60 +++++ .../async/rabbit/config/RabbitMqConfig.java | 5 +- .../async/rabbit/config/DomainHandlers.java | 2 +- .../rabbit/config/HandlerResolverBuilder.java | 28 +-- .../async/rabbit/config/RabbitMqConfig.java | 14 +- .../config/CommandListenersConfigTest.java | 4 +- .../config/EventListenersConfigTest.java | 2 +- .../NotificationListenersConfigTest.java | 2 +- .../config/QueryListenerConfigTest.java | 2 +- .../async-commons-rabbit-starter.gradle | 2 + .../rabbit/config/CommandListenersConfig.java | 2 +- .../rabbit/config/EventListenersConfig.java | 2 +- .../config/NotificacionListenersConfig.java | 4 +- .../rabbit/config/QueryListenerConfig.java | 2 +- .../async/rabbit/config/RabbitMqConfig.java | 84 +------ .../config/CommandListenersConfigTest.java | 2 +- .../config/EventListenersConfigTest.java | 2 +- .../NotificacionListenersConfigTest.java | 2 +- .../config/QueryListenerConfigTest.java | 2 +- async/async-rabbit/async-rabbit.gradle | 2 +- .../async/rabbit/DynamicRegistryImp.java | 4 +- .../rabbit/RabbitDirectAsyncGateway.java | 70 ++++-- .../async/rabbit/RabbitDomainEventBus.java | 6 +- .../async/rabbit/RabbitMessage.java | 8 +- .../communications/ReactiveMessageSender.java | 3 +- .../JacksonCloudEventMessageConverter.java | 205 ------------------ .../json/RabbitJacksonMessageConverter.java | 38 ++++ .../listeners/ApplicationCommandListener.java | 39 +++- .../listeners/ApplicationEventListener.java | 21 +- .../ApplicationNotificationListener.java | 24 +- .../listeners/ApplicationQueryListener.java | 20 +- .../listeners/GenericMessageListener.java | 33 ++- .../async/rabbit/DynamicRegistryImpTest.java | 11 +- .../async/rabbit/HandlerResolverTest.java | 15 +- .../rabbit/RabbitDirectAsyncGatewayTest.java | 5 +- .../ReactiveMessageSenderTest.java | 5 +- .../json/CloudEventBuilderExtTest.java | 1 + ...JacksonCloudEventMessageConverterTest.java | 171 --------------- .../json/JacksonMessageConverterTest.java | 34 ++- .../rabbit/converters/json/SampleClass.java | 16 +- .../async/rabbit/ext/CustomReporterTest.java | 3 +- .../ApplicationCommandListenerPerfTest.java | 61 +++--- .../ApplicationCommandListenerTest.java | 2 +- .../ApplicationEventListenerTest.java | 2 +- .../ApplicationNotificationListenerTest.java | 2 +- .../ApplicationQueryListenerErrorTest.java | 2 +- .../ApplicationQueryListenerTest.java | 30 ++- .../ListenerReporterTestSuperClass.java | 22 +- domain/domain-events/domain-events-api.gradle | 1 + gradle.properties | 2 +- .../src/main/java/sample/HandlersConfig.java | 3 +- .../src/main/java/sample/UseCase.java | 2 +- .../java/sample/SampleRestController.java | 2 +- .../java/sample/SampleRestController.java | 2 +- 84 files changed, 795 insertions(+), 863 deletions(-) create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java rename async/{async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java => async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java} (60%) rename async/{async-rabbit/src/main/java/org/reactivecommons/async/rabbit => async-commons/src/main/java/org/reactivecommons/async/commons}/HandlerResolver.java (58%) rename async/{async-rabbit/src/main/java/org/reactivecommons/async/rabbit => async-commons/src/main/java/org/reactivecommons/async/commons}/converters/json/CloudEventBuilderExt.java (91%) rename async/{async-rabbit/src/main/java/org/reactivecommons/async/rabbit => async-commons/src/main/java/org/reactivecommons/async/commons}/converters/json/JacksonMessageConverter.java (77%) create mode 100644 async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java create mode 100644 async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java delete mode 100644 async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java create mode 100644 async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java delete mode 100644 async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java index f91c01f6..cb56377a 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java @@ -4,6 +4,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DynamicRegistry; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; @@ -36,7 +37,7 @@ class DynamicRegistryTest { @Test void shouldReceiveResponse() { UnicastProcessor result = UnicastProcessor.create(); - EventHandler fn = message -> fromRunnable(() -> result.onNext(message.getData())); + DomainEventHandler fn = message -> fromRunnable(() -> result.onNext(message.getData())); dynamicRegistry.listenEvent("test.event", fn, String.class).block(); final Publisher emit = eventBus.emit(new DomainEvent<>("test.event", "42", "Hello")); diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java index ae8b2419..d8e26034 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java @@ -6,6 +6,7 @@ import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -81,7 +82,7 @@ public UnicastProcessor> listener() { return UnicastProcessor.create(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(UnicastProcessor> listener) { return command -> { listener.onNext(command); return empty(); diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java index 3ff83c83..e700f1ab 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java @@ -4,6 +4,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; @@ -65,7 +66,7 @@ public UnicastProcessor> listener() { return UnicastProcessor.create(); } - private EventHandler handle(UnicastProcessor> listener) { + private DomainEventHandler handle(UnicastProcessor> listener) { return command -> { listener.onNext(command); return empty(); diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java index 75808a2f..aa602602 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java @@ -5,6 +5,7 @@ import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -89,7 +90,7 @@ public UnicastProcessor> listener() { return UnicastProcessor.create(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(UnicastProcessor> listener) { return command -> { return fromRunnable(() -> { // out.println("Received at: " + System.currentTimeMillis()/1000); diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java index b41711bd..5d578c1a 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java @@ -6,6 +6,7 @@ import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -91,7 +92,7 @@ public UnicastProcessor> listener() { return UnicastProcessor.create(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(UnicastProcessor> listener) { return command -> { // out.println("Received at: " + System.currentTimeMillis()/1000); try { diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java index b359aaa2..fc9b4f50 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java @@ -6,6 +6,7 @@ import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -88,7 +89,7 @@ public UnicastProcessor> listener() { return UnicastProcessor.create(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(UnicastProcessor> listener) { return command -> { out.println("Received at: " + System.currentTimeMillis() / 1000); out.println("Received in: " + Thread.currentThread().getName()); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java index ba21dffe..51eefc1a 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java @@ -1,6 +1,6 @@ package org.reactivecommons.async.api; -import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; -public interface DefaultCommandHandler extends CommandHandler { +public interface DefaultCommandHandler extends DomainCommandHandler { } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java index 1501035b..661b5349 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java @@ -17,8 +17,12 @@ public interface DirectAsyncGateway { Mono sendCommand(CloudEvent command, String targetName); + Mono sendCommand(CloudEvent command, String targetName, long delayMillis); + Mono sendCommand(CloudEvent command, String targetName, String domain); + Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain); + Mono requestReply(AsyncQuery query, String targetName, Class type); Mono requestReply(AsyncQuery query, String targetName, Class type, String domain); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java index 043f95c6..39ebe722 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java @@ -1,5 +1,6 @@ package org.reactivecommons.async.api; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; @@ -8,7 +9,7 @@ public interface DynamicRegistry { @Deprecated - Mono listenEvent(String eventName, EventHandler fn, Class eventClass); + Mono listenEvent(String eventName, DomainEventHandler fn, Class eventClass); void serveQuery(String resource, QueryHandler handler, Class queryClass); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java index d5e35fc3..67ae2a08 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java @@ -1,13 +1,13 @@ package org.reactivecommons.async.api; import io.cloudevents.CloudEvent; -import io.cloudevents.core.provider.EventFormatProvider; -import io.cloudevents.jackson.JsonFormat; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.reactivecommons.async.api.handlers.CommandHandler; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.CloudCommandHandler; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; @@ -24,11 +24,11 @@ @NoArgsConstructor(access = AccessLevel.PACKAGE) public class HandlerRegistry { public static final String DEFAULT_DOMAIN = "app"; - private final Map>> domainEventListeners = new ConcurrentHashMap<>(); - private final List> dynamicEventHandlers = new CopyOnWriteArrayList<>(); - private final List> eventNotificationListener = new CopyOnWriteArrayList<>(); + private final Map>> domainEventListeners = new ConcurrentHashMap<>(); + private final List> dynamicEventHandlers = new CopyOnWriteArrayList<>(); + private final List> eventNotificationListener = new CopyOnWriteArrayList<>(); private final List> handlers = new CopyOnWriteArrayList<>(); - private final List> commandHandlers = new CopyOnWriteArrayList<>(); + private final List> commandHandlers = new CopyOnWriteArrayList<>(); public static HandlerRegistry register() { @@ -37,75 +37,103 @@ public static HandlerRegistry register() { return instance; } - public HandlerRegistry listenDomainEvent(String domain, String eventName, EventHandler handler, Class eventClass) { + public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, Class eventClass) { domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, eventClass)); + .add(new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } - public HandlerRegistry listenEvent(String eventName, EventHandler handler, Class eventClass) { + public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, CloudEventHandler handler) { + domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) + .add(new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); + return this; + } + + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) { domainEventListeners.computeIfAbsent(DEFAULT_DOMAIN, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, eventClass)); + .add(new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } - public HandlerRegistry listenEvent(String eventName, EventHandler handler) { - return listenEvent(eventName, handler, inferGenericParameterType(handler)); + public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler handler) { + domainEventListeners.computeIfAbsent(DEFAULT_DOMAIN, ignored -> new CopyOnWriteArrayList<>()) + .add(new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); + return this; } - public HandlerRegistry listenNotificationEvent(String eventName, EventHandler handler, Class eventClass) { + public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, Class eventClass) { eventNotificationListener.add(new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } - public HandlerRegistry handleDynamicEvents(String eventNamePattern, EventHandler handler, Class eventClass) { + public HandlerRegistry listenNotificationCloudEvent(String eventName, CloudEventHandler handler) { + eventNotificationListener.add(new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); + return this; + } + + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, Class eventClass) { dynamicEventHandlers.add(new RegisteredEventListener<>(eventNamePattern, handler, eventClass)); return this; } - public HandlerRegistry handleDynamicEvents(String eventNamePattern, EventHandler handler) { - return handleDynamicEvents(eventNamePattern, handler, inferGenericParameterType(handler)); + public HandlerRegistry handleDynamicCloudEvents(String eventNamePattern, CloudEventHandler handler) { + dynamicEventHandlers.add(new RegisteredEventListener<>(eventNamePattern, handler, CloudEvent.class)); + return this; } - public HandlerRegistry handleCommand(String commandName, CommandHandler fn, Class commandClass) { + public HandlerRegistry handleCommand(String commandName, DomainCommandHandler fn, Class commandClass) { commandHandlers.add(new RegisteredCommandHandler<>(commandName, fn, commandClass)); return this; } - public HandlerRegistry handleCommand(String commandName, CommandHandler fn) { - commandHandlers.add(new RegisteredCommandHandler<>(commandName, fn, inferGenericParameterType(fn))); + public HandlerRegistry handleCloudEventCommand(String commandName, CloudCommandHandler handler) { + commandHandlers.add(new RegisteredCommandHandler<>(commandName, handler, CloudEvent.class)); return this; } - public HandlerRegistry serveQuery(String resource, QueryHandler handler) { - return serveQuery(resource, handler, inferGenericParameterType(handler)); + public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) { + handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass)); + return this; } - public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) { - if(queryClass == CloudEvent.class){ - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> - { - CloudEvent query = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .deserialize(message); + public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) { + handlers.add(new RegisteredQueryHandler<>(resource, handler, queryClass)); + return this; + } - return handler.handle((R) query); + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandler handler) { + handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), CloudEvent.class)); + return this; + } - } , byte[].class)); - } - else{ - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass)); - } + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandlerDelegate handler) { + handlers.add(new RegisteredQueryHandler<>(resource, handler, CloudEvent.class)); return this; } - public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) { - handlers.add(new RegisteredQueryHandler<>(resource, handler, queryClass)); + + @Deprecated(forRemoval = true) + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler) { + return listenEvent(eventName, handler, inferGenericParameterType(handler)); + } + + @Deprecated(forRemoval = true) + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler) { + return handleDynamicEvents(eventNamePattern, handler, inferGenericParameterType(handler)); + } + + @Deprecated(forRemoval = true) + public HandlerRegistry handleCommand(String commandName, DomainCommandHandler handler) { + commandHandlers.add(new RegisteredCommandHandler<>(commandName, handler, inferGenericParameterType(handler))); return this; } + @Deprecated(forRemoval = true) + public HandlerRegistry serveQuery(String resource, QueryHandler handler) { + return serveQuery(resource, handler, inferGenericParameterType(handler)); + } + @Deprecated(forRemoval = true) @SuppressWarnings("unchecked") private Class inferGenericParameterType(QueryHandler handler) { try { @@ -117,8 +145,9 @@ private Class inferGenericParameterType(QueryHandler handler) { } } + @Deprecated(forRemoval = true) @SuppressWarnings("unchecked") - private Class inferGenericParameterType(CommandHandler handler) { + private Class inferGenericParameterType(DomainCommandHandler handler) { try { ParameterizedType genericSuperclass = (ParameterizedType) handler.getClass().getGenericInterfaces()[0]; return (Class) genericSuperclass.getActualTypeArguments()[0]; @@ -128,7 +157,8 @@ private Class inferGenericParameterType(CommandHandler handler) { } } - private Class inferGenericParameterType(EventHandler handler) { + @Deprecated(forRemoval = true) + private Class inferGenericParameterType(DomainEventHandler handler) { try { ParameterizedType genericSuperclass = (ParameterizedType) handler.getClass().getGenericInterfaces()[0]; return (Class) genericSuperclass.getActualTypeArguments()[0]; diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java new file mode 100644 index 00000000..294a4dd8 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import io.cloudevents.CloudEvent; + +public interface CloudCommandHandler extends CommandHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java new file mode 100644 index 00000000..86878903 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import io.cloudevents.CloudEvent; + +public interface CloudEventHandler extends EventHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java index 4b84b53d..887267cd 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java @@ -1,6 +1,4 @@ package org.reactivecommons.async.api.handlers; -import org.reactivecommons.api.domain.Command; - -public interface CommandHandler extends GenericHandler> { +public interface CommandHandler extends GenericHandler { } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java new file mode 100644 index 00000000..8ddf34ad --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.Command; + +public interface DomainCommandHandler extends CommandHandler> { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java new file mode 100644 index 00000000..ff0d929c --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.DomainEvent; + +public interface DomainEventHandler extends EventHandler>{ +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java index 038251fb..45041bb0 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java @@ -1,6 +1,4 @@ package org.reactivecommons.async.api.handlers; -import org.reactivecommons.api.domain.DomainEvent; - -public interface EventHandler extends GenericHandler> { +public interface EventHandler extends GenericHandler { } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java index 290086ab..adc1c694 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java @@ -6,8 +6,8 @@ @RequiredArgsConstructor @Getter -public class RegisteredCommandHandler { +public class RegisteredCommandHandler { private final String path; - private final CommandHandler handler; + private final CommandHandler handler; private final Class inputClass; } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java index e0d2103f..48e66bc2 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java @@ -6,8 +6,8 @@ @RequiredArgsConstructor @Getter -public class RegisteredEventListener { +public class RegisteredEventListener { private final String path; - private final EventHandler handler; + private final EventHandler handler; private final Class inputClass; } diff --git a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java index 121feeb8..0f73e4b1 100644 --- a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java +++ b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java @@ -1,14 +1,11 @@ package org.reactivecommons.async.api; +import io.cloudevents.CloudEvent; import lombok.Data; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.handlers.CommandHandler; -import org.reactivecommons.async.api.handlers.EventHandler; -import org.reactivecommons.async.api.handlers.QueryHandler; -import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.*; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; @@ -21,46 +18,65 @@ class HandlerRegistryTest { private final HandlerRegistry registry = HandlerRegistry.register(); private final String name = "some.event"; + private final String domain = "some-domain"; + @Test - void shouldListenEventWithTypeInferenceWhenClassInstanceIsUsed() { - SomeEventHandler eventHandler = new SomeEventHandler(); + void shouldListenDomainEvent() { + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); - registry.listenEvent(name, eventHandler); + registry.listenDomainEvent(domain, name, eventHandler, SomeDataClass.class); - assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) + assertThat(registry.getDomainEventListeners().get(domain)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); } @Test - void shouldRegisterPatternEventHandlerWithTypeInference() { - SomeEventHandler eventHandler = new SomeEventHandler(); + void shouldListenDomainCloudEvent() { + SomeCloudEventHandler eventHandler = new SomeCloudEventHandler(); - String eventNamePattern = "a.*"; + registry.listenDomainCloudEvent(domain, name, eventHandler); - HandlerRegistry resultRegistry = registry.listenEvent(eventNamePattern, eventHandler); - RegisteredEventListener expectedRegisteredEventListener = - new RegisteredEventListener<>(eventNamePattern, eventHandler, SomeDataClass.class); + assertThat(registry.getDomainEventListeners().get(domain)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); + } + + @Test + void shouldListenEvent() { + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); + + registry.listenEvent(name, eventHandler, SomeDataClass.class); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) - .anySatisfy(registeredEventListener -> assertThat(registeredEventListener) - .usingRecursiveComparison() - .isEqualTo(expectedRegisteredEventListener)); + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); + } - assertThat(resultRegistry) - .isSameAs(registry); + @Test + void shouldListenCloudEvent() { + SomeCloudEventHandler eventHandler = new SomeCloudEventHandler(); + + registry.listenCloudEvent(name, eventHandler); + + assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); } @Test void shouldRegisterPatternEventHandler() { - SomeEventHandler eventHandler = new SomeEventHandler(); + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); String eventNamePattern = "a.*"; HandlerRegistry resultRegistry = registry.listenEvent(eventNamePattern, eventHandler, SomeDataClass.class); - RegisteredEventListener expectedRegisteredEventListener = + RegisteredEventListener> expectedRegisteredEventListener = new RegisteredEventListener<>(eventNamePattern, eventHandler, SomeDataClass.class); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) @@ -79,10 +95,17 @@ void shouldRegisterNotificationEventListener() { .anySatisfy(listener -> assertThat(listener.getPath()).isEqualTo(name)); } + @Test + void shouldRegisterNotificationCloudEventListener() { + registry.listenNotificationCloudEvent(name, message -> Mono.empty()); + assertThat(registry.getEventNotificationListener()) + .anySatisfy(listener -> assertThat(listener.getPath()).isEqualTo(name)); + } + @Test @SuppressWarnings("unchecked") public void listenEvent() { - EventHandler handler = mock(EventHandler.class); + SomeDomainEventHandler handler = mock(SomeDomainEventHandler.class); registry.listenEvent(name, handler, SomeDataClass.class); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) @@ -92,10 +115,34 @@ public void listenEvent() { } @Test - void handleCommandWithTypeInference() { - SomeCommandHandler handler = new SomeCommandHandler(); + void shouldListenDynamicEvent() { + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); + + registry.handleDynamicEvents(name, eventHandler, SomeDataClass.class); - registry.handleCommand(name, handler); + assertThat(registry.getDynamicEventHandlers()) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); + } + + @Test + void shouldListenDynamicCloudEvent() { + SomeCloudEventHandler eventHandler = new SomeCloudEventHandler(); + + registry.handleDynamicCloudEvents(name, eventHandler); + + assertThat(registry.getDynamicEventHandlers()) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); + } + + @Test + void handleDomainCommand() { + SomeDomainCommandHandler handler = new SomeDomainCommandHandler<>(); + + registry.handleCommand(name, handler, SomeDataClass.class); assertThat(registry.getCommandHandlers()) .anySatisfy(registered -> assertThat(registered) @@ -104,24 +151,27 @@ void handleCommandWithTypeInference() { } @Test - void handleCommandWithoutTypeShouldFail() { - Assertions.assertThrows( - RuntimeException.class, - () -> registry.handleCommand(name, (Command message) -> Mono.empty())); - } + void handleCloudEventCommand() { + SomeCloudEventCommandHandler cloudCommandHandler = new SomeCloudEventCommandHandler(); - @Test - void listenEventWithoutTypeShouldFail() { - Assertions.assertThrows( - RuntimeException.class, - () -> registry.listenEvent(name, (DomainEvent message) -> Mono.empty())); + registry.handleCloudEventCommand(name, cloudCommandHandler); + + assertThat(registry.getCommandHandlers()) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, RegisteredCommandHandler::getHandler) + .containsExactly(name, CloudEvent.class, cloudCommandHandler)).hasSize(1); } @Test - void handleQueryWithoutTypeShouldFail() { - Assertions.assertThrows( - RuntimeException.class, - () -> registry.serveQuery(name, (SomeDataClass query) -> Mono.empty())); + void shouldServerCloudEventQuery() { + SomeCloudEventQueryHandler queryHandler = new SomeCloudEventQueryHandler(); + + registry.serveCloudEventQuery(name, queryHandler); + + assertThat(registry.getHandlers()) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) + .containsExactly(name, CloudEvent.class)).hasSize(1); } @Test @@ -147,7 +197,7 @@ void serveQueryWithLambda() { @Test void serveQueryWithTypeInference() { QueryHandler handler = new SomeQueryHandler(); - registry.serveQuery(name, handler); + registry.serveQuery(name, handler,SomeDataClass.class); assertThat(registry.getHandlers()).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); @@ -181,20 +231,34 @@ public Mono handle(From from, SomeDataClass message) { } } - private static class SomeEventHandler implements EventHandler { + private static class SomeDomainEventHandler implements DomainEventHandler { @Override public Mono handle(DomainEvent message) { return Mono.empty(); } } - private static class SomeCommandHandler implements CommandHandler { + private static class SomeCloudEventHandler implements CloudEventHandler { + @Override + public Mono handle(CloudEvent message) { + return null; + } + } + + private static class SomeDomainCommandHandler implements DomainCommandHandler { @Override public Mono handle(Command message) { return Mono.empty(); } } + private static class SomeCloudEventCommandHandler implements CloudCommandHandler { + @Override + public Mono handle(CloudEvent message) { + return null; + } + } + private static class SomeQueryHandler implements QueryHandler { @Override public Mono handle(SomeDataClass message) { @@ -202,6 +266,13 @@ public Mono handle(SomeDataClass message) { } } + private static class SomeCloudEventQueryHandler implements QueryHandler { + @Override + public Mono handle(CloudEvent message) { + return Mono.empty(); + } + } + @Data private static class SomeDataClass { diff --git a/async/async-commons/async-commons.gradle b/async/async-commons/async-commons.gradle index 567820c6..d4380d7d 100644 --- a/async/async-commons/async-commons.gradle +++ b/async/async-commons/async-commons.gradle @@ -8,9 +8,9 @@ dependencies { api project(':domain-events-api') compileOnly 'io.projectreactor:reactor-core' - api 'io.projectreactor.rabbitmq:reactor-rabbitmq:1.5.6' api 'com.fasterxml.jackson.core:jackson-databind' implementation 'commons-io:commons-io:2.16.1' + 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/CommandExecutor.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java index 174ac95f..4ceb6641 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java @@ -1,23 +1,18 @@ package org.reactivecommons.async.commons; - -import org.reactivecommons.api.domain.Command; +import lombok.AllArgsConstructor; import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.commons.communications.Message; import reactor.core.publisher.Mono; import java.util.function.Function; +@AllArgsConstructor public class CommandExecutor { private final CommandHandler eventHandler; - private final Function> converter; - - public CommandExecutor(CommandHandler eventHandler, Function> converter) { - this.eventHandler = eventHandler; - this.converter = converter; - } + private final Function converter; - public Mono execute(Message rawMessage){ + public Mono execute(Message rawMessage) { return eventHandler.handle(converter.apply(rawMessage)); } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java similarity index 60% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java index 7eefa165..78510323 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java @@ -1,44 +1,49 @@ -package org.reactivecommons.async.rabbit; +package org.reactivecommons.async.commons; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.java.Log; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.exceptions.MessageConversionException; -import org.reactivecommons.async.commons.DiscardNotifier; import reactor.core.publisher.Mono; -import java.io.IOException; import java.util.logging.Level; import static java.lang.String.format; +import static org.reactivecommons.async.commons.converters.json.JacksonMessageConverter.APPLICATION_CLOUD_EVENT_JSON; @Log -public class RabbitDiscardNotifier implements DiscardNotifier { - +@AllArgsConstructor +public class DLQDiscardNotifier implements DiscardNotifier { private final DomainEventBus eventBus; - private final ObjectMapper objectMapper; - - public RabbitDiscardNotifier(DomainEventBus eventBus, ObjectMapper objectMapper) { - this.eventBus = eventBus; - this.objectMapper = objectMapper; - } + private final MessageConverter messageConverter; @Override public Mono notifyDiscard(Message message) { try { return notify(message).onErrorResume(this::onError); - }catch (Exception e){ + } catch (Exception e) { return onError(e); } } - private Mono notify(Message message){ + private Mono notify(Message message) { + if (isCloudEvent(message)) { + CloudEvent cloudEvent = messageConverter.readCloudEvent(message); + String dlqType = cloudEvent.getType() + ".dlq"; + CloudEvent forDlq = CloudEventBuilder.from(cloudEvent) + .withType(dlqType) + .build(); + return Mono.from(eventBus.emit(forDlq)); + } try { - JsonSkeleton node = readSkeleton(message); + JsonSkeleton node = messageConverter.readValue(message, JsonSkeleton.class); return Mono.from(eventBus.emit(createEvent(node))); } catch (MessageConversionException e) { return notifyUnreadableMessage(message, e); @@ -47,9 +52,9 @@ private Mono notify(Message message){ private Mono notifyUnreadableMessage(Message message, MessageConversionException e) { String bodyString; - try{ + try { bodyString = new String(message.getBody()); - }catch (Exception ex){ + } catch (Exception ex) { bodyString = "Opaque binary Message, unable to decode: " + ex.getMessage(); } log.log(Level.SEVERE, format("Unable to interpret discarded message: %s", bodyString), e); @@ -57,32 +62,28 @@ private Mono notifyUnreadableMessage(Message message, MessageConversionExc return Mono.from(eventBus.emit(event)); } - private Mono onError(Throwable e){ + private Mono onError(Throwable e) { log.log(Level.SEVERE, "FATAL!! unable to notify Discard of message!!", e); return Mono.empty(); } - private JsonSkeleton readSkeleton(Message message) { - try { - return objectMapper.readValue(message.getBody(), JsonSkeleton.class); - } catch (IOException e) { - throw new MessageConversionException(e); - } - } - - private DomainEvent createEvent(JsonSkeleton skeleton) { if (skeleton.isCommand()) { - return new DomainEvent<>(skeleton.name+".dlq", skeleton.commandId, skeleton.data); + return new DomainEvent<>(skeleton.name + ".dlq", skeleton.commandId, skeleton.data); } else if (skeleton.isEvent()) { - return new DomainEvent<>(skeleton.name+".dlq", skeleton.eventId, skeleton.data); + return new DomainEvent<>(skeleton.name + ".dlq", skeleton.eventId, skeleton.data); } else if (skeleton.isQuery()) { - return new DomainEvent<>(skeleton.resource+".dlq", skeleton.resource+"query", skeleton.queryData); + return new DomainEvent<>(skeleton.resource + ".dlq", skeleton.resource + "query", skeleton.queryData); } else { throw new MessageConversionException("Fail to math message type"); } } + private boolean isCloudEvent(Message message) { + return message.getProperties().getContentType() != null + && message.getProperties().getContentType().contains(APPLICATION_CLOUD_EVENT_JSON); + } + @Data private static class JsonSkeleton { private String name; @@ -101,7 +102,7 @@ public boolean isCommand() { } public boolean isQuery() { - return !empty(resource) && queryData != null; + return !empty(resource) && queryData != null; } private boolean empty(String str) { diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java index 9a362707..fef94e99 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java @@ -1,23 +1,18 @@ package org.reactivecommons.async.commons; - -import org.reactivecommons.api.domain.DomainEvent; +import lombok.AllArgsConstructor; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.commons.communications.Message; import reactor.core.publisher.Mono; import java.util.function.Function; +@AllArgsConstructor public class EventExecutor { private final EventHandler eventHandler; - private final Function> converter; - - public EventExecutor(EventHandler eventHandler, Function> converter) { - this.eventHandler = eventHandler; - this.converter = converter; - } + private final Function converter; - public Mono execute(Message rawMessage){ + public Mono execute(Message rawMessage) { return eventHandler.handle(converter.apply(rawMessage)); } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/HandlerResolver.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java similarity index 58% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/HandlerResolver.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java index 009e18a8..17bbe0c4 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/HandlerResolver.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit; +package org.reactivecommons.async.commons; import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; @@ -9,6 +9,7 @@ import org.reactivecommons.async.commons.utils.matcher.Matcher; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.function.Function; @@ -17,10 +18,11 @@ public class HandlerResolver { private final Map> queryHandlers; - private final Map> eventListeners; - private final Map> eventsToBind; - private final Map> eventNotificationListeners; - private final Map> commandHandlers; + private final Map> eventListeners; + private final Map> eventsToBind; + private final Map> eventNotificationListeners; + private final Map> commandHandlers; + private final Matcher matcher = new KeyMatcher(); @SuppressWarnings("unchecked") @@ -30,40 +32,47 @@ public RegisteredQueryHandler getQueryHandler(String path) { } @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - return (RegisteredCommandHandler) commandHandlers + public RegisteredCommandHandler getCommandHandler(String path) { + return (RegisteredCommandHandler) commandHandlers .computeIfAbsent(path, getMatchHandler(commandHandlers)); } @SuppressWarnings("unchecked") - public RegisteredEventListener getEventListener(String path) { + public RegisteredEventListener getEventListener(String path) { if (eventListeners.containsKey(path)) { - return (RegisteredEventListener) eventListeners.get(path); + return (RegisteredEventListener) eventListeners.get(path); } - return (RegisteredEventListener) getMatchHandler(eventListeners).apply(path); + return (RegisteredEventListener) getMatchHandler(eventListeners).apply(path); } - - public Collection> getNotificationListeners() { + public Collection> getNotificationListeners() { return eventNotificationListeners.values(); } @SuppressWarnings("unchecked") - public RegisteredEventListener getNotificationListener(String path) { - return (RegisteredEventListener) eventNotificationListeners + public RegisteredEventListener getNotificationListener(String path) { + return (RegisteredEventListener) eventNotificationListeners .computeIfAbsent(path, getMatchHandler(eventNotificationListeners)); } // Returns only the listenEvent not the handleDynamicEvents - public Collection> getEventListeners() { + public Collection> getEventListeners() { return eventsToBind.values(); } - void addEventListener(RegisteredEventListener listener) { + public List getEventNames() { + return List.copyOf(eventListeners.keySet()); + } + + public List getNotificationNames() { + return List.copyOf(eventNotificationListeners.keySet()); + } + + public void addEventListener(RegisteredEventListener listener) { eventListeners.put(listener.getPath(), listener); } - void addQueryHandler(RegisteredQueryHandler handler) { + public void addQueryHandler(RegisteredQueryHandler handler) { if (handler.getPath().contains("*") || handler.getPath().contains("#")) { throw new RuntimeException("avoid * or # in dynamic handlers, make sure you have no conflicts with cached patterns"); } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java index 663a6251..42b12b5c 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java @@ -4,17 +4,32 @@ /** * Simple Internal Message representation + * * @author Daniel Bustamante Ospina */ public interface Message { byte[] getBody(); + Properties getProperties(); interface Properties { String getContentType(); - String getContentEncoding(); + + default String getContentEncoding() { + return null; + } + long getContentLength(); + Map getHeaders(); + + default String getKey() { + return null; + } + + default String getTopic() { + return null; + } } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java index 2202ab5b..3c13124b 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java @@ -14,10 +14,14 @@ public interface MessageConverter { Command readCommand(Message message, Class bodyClass); + CloudEvent readCloudEvent(Message message); + T readValue(Message message, Class valueClass); Command readCommandStructure(Message message); + DomainEvent readDomainEventStructure(Message message); + AsyncQuery readAsyncQueryStructure(Message message); Message toMessage(Object object); diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExt.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java similarity index 91% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExt.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java index 88570b9d..6a80cb7b 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExt.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.converters.json; +package org.reactivecommons.async.commons.converters.json; import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java index 163cde62..ac6fc677 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.jackson.JsonFormat; public class DefaultObjectMapperSupplier implements ObjectMapperSupplier { @@ -9,7 +10,8 @@ public class DefaultObjectMapperSupplier implements ObjectMapperSupplier { public ObjectMapper get() { final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - + objectMapper.findAndRegisterModules(); + objectMapper.registerModule(JsonFormat.getCloudEventJacksonModule()); // TODO: Review if this is necessary return objectMapper; } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java similarity index 77% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverter.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java index 0872dd85..5d0cbd70 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java @@ -1,29 +1,28 @@ -package org.reactivecommons.async.rabbit.converters.json; +package org.reactivecommons.async.commons.converters.json; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.jackson.JsonFormat; import lombok.Data; +import lombok.RequiredArgsConstructor; 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.converters.MessageConverter; import org.reactivecommons.async.commons.exceptions.MessageConversionException; -import org.reactivecommons.async.rabbit.RabbitMessage; import java.io.IOException; -import java.nio.charset.StandardCharsets; -public class JacksonMessageConverter implements MessageConverter { - private static final String CONTENT_TYPE = "application/json"; +@RequiredArgsConstructor +public abstract class JacksonMessageConverter implements MessageConverter { public static final String FAILED_TO_CONVERT_MESSAGE_CONTENT = "Failed to convert Message content"; + public static final String CONTENT_TYPE = "content-type"; + public static final String APPLICATION_CLOUD_EVENT_JSON = "application/cloudevents+json"; + public static final String APPLICATION_JSON = "application/json"; - private final ObjectMapper objectMapper; - - - public JacksonMessageConverter(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } + protected final ObjectMapper objectMapper; @Override public AsyncQuery readAsyncQuery(Message message, Class bodyClass) { @@ -58,6 +57,11 @@ public Command readCommand(Message message, Class bodyClass) { } } + @Override + public CloudEvent readCloudEvent(Message message) { + return readValue(message, CloudEvent.class); + } + @Override public T readValue(Message message, Class valueClass) { try { @@ -88,22 +92,6 @@ public AsyncQuery readAsyncQueryStructure(Message message) { return new AsyncQuery<>(asyncQueryJson.getResource(), (T) asyncQueryJson.getQueryData()); } - @Override - public Message toMessage(Object object) { - byte[] bytes; - try { - String jsonString = this.objectMapper.writeValueAsString(object); - bytes = jsonString.getBytes(StandardCharsets.UTF_8); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - RabbitMessage.RabbitMessageProperties props = new RabbitMessage.RabbitMessageProperties(); - props.setContentType(CONTENT_TYPE); - props.setContentEncoding(StandardCharsets.UTF_8.name()); - props.setContentLength(bytes.length); - return new RabbitMessage(bytes, props); - } - @Data private static class AsyncQueryJson { private String resource; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java new file mode 100644 index 00000000..2b4b359f --- /dev/null +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.commons.ext; + +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 reactor.core.publisher.Mono; + +public class DefaultCustomReporter implements 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(); + } +} diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java new file mode 100644 index 00000000..47e0e11b --- /dev/null +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java @@ -0,0 +1,60 @@ +package org.reactivecommons.async.commons.utils.resolver; + +import lombok.experimental.UtilityClass; +import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Stream; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@UtilityClass +public class HandlerResolverUtil { + + public static HandlerResolver fromHandlerRegistries(Collection registries, + CommandHandler defaultHandler) { + final ConcurrentMap> queryHandlers = registries.stream() + .flatMap(r -> r.getHandlers().stream()) + .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), + ConcurrentHashMap::putAll); + + final ConcurrentMap> eventsToBind = registries.stream() + .flatMap(r -> r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()) + .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), + ConcurrentHashMap::putAll); + + // event handlers and dynamic handlers + final ConcurrentMap> eventHandlers = registries.stream() + .flatMap(r -> Stream.concat(r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream(), + r.getDynamicEventHandlers().stream())) + .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), + ConcurrentHashMap::putAll); + + final ConcurrentMap> commandHandlers = registries.stream() + .flatMap(r -> r.getCommandHandlers().stream()) + .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), + ConcurrentHashMap::putAll); + + final ConcurrentMap> eventNotificationListener = registries.stream() + .flatMap(r -> r.getEventNotificationListener().stream()) + .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), + ConcurrentHashMap::putAll); + + return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, + commandHandlers) { + @Override + @SuppressWarnings("unchecked") + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = super.getCommandHandler(path); + return handler != null ? handler : new RegisteredCommandHandler<>("", defaultHandler, Object.class); + } + }; + } +} diff --git a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java index 0d930391..6000a897 100644 --- a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ b/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java @@ -6,8 +6,9 @@ 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.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import reactor.core.publisher.Mono; import reactor.rabbitmq.*; import reactor.util.retry.Retry; @@ -58,7 +59,7 @@ public ConnectionFactoryProvider connectionFactory(RabbitProperties properties) } public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new JacksonMessageConverter(objectMapperSupplier.get()); + return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); } Mono createSenderConnectionMono(ConnectionFactory factory, String name) { diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java index 120649b0..aed074ce 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java +++ b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java @@ -1,6 +1,6 @@ package org.reactivecommons.async.rabbit.config; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import java.util.Map; import java.util.TreeMap; diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java index 08f3bf0e..cff55f7b 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java +++ b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java @@ -8,7 +8,7 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -32,48 +32,48 @@ public static HandlerResolver buildResolver(String domain, .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); - final ConcurrentMap> commandHandlers = registries + final ConcurrentMap> commandHandlers = registries .values().stream() .flatMap(r -> r.getCommandHandlers().stream()) .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); - final ConcurrentMap> eventNotificationListener = registries + final ConcurrentMap> eventNotificationListener = registries .values() .stream() .flatMap(r -> r.getEventNotificationListener().stream()) .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); - final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); + final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); - final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); + final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, commandHandlers) { @Override @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = super.getCommandHandler(path); return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); } }; } - final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); - final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); + final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); + final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); return new HandlerResolver(new ConcurrentHashMap<>(), eventHandlers, eventsToBind, new ConcurrentHashMap<>(), new ConcurrentHashMap<>()) { @Override @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = super.getCommandHandler(path); return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); } }; } - private static ConcurrentMap> getEventHandlersWithDynamics(String domain, Map registries) { + private static ConcurrentMap> getEventHandlersWithDynamics(String domain, Map registries) { // event handlers and dynamic handlers return registries .values().stream() @@ -88,14 +88,14 @@ private static ConcurrentMap> getEventHandler ConcurrentHashMap::putAll); } - private static Stream> getDynamics(String domain, HandlerRegistry r) { + private static Stream> getDynamics(String domain, HandlerRegistry r) { if (DEFAULT_DOMAIN.equals(domain)) { return r.getDynamicEventHandlers().stream(); } return Stream.of(); } - private static ConcurrentMap> getEventsToBind(String domain, Map registries) { + private static ConcurrentMap> getEventsToBind(String domain, Map registries) { return registries .values().stream() .flatMap(r -> { diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java index ef282676..8efba6ce 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java @@ -13,7 +13,9 @@ 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.communications.Message; import org.reactivecommons.async.commons.config.BrokerConfig; import org.reactivecommons.async.commons.config.IBrokerConfigProps; @@ -22,8 +24,6 @@ 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.HandlerResolver; -import org.reactivecommons.async.rabbit.RabbitDiscardNotifier; import org.reactivecommons.async.rabbit.RabbitDomainEventBus; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; @@ -32,7 +32,7 @@ import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.converters.json.JacksonCloudEventMessageConverter; +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; @@ -72,18 +72,18 @@ public class RabbitMqConfig { @Bean public ConnectionManager buildConnectionManager(AsyncPropsDomain props, MessageConverter converter, - BrokerConfig brokerConfig, ObjectMapperSupplier objectMapperSupplier) { + 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 RabbitDiscardNotifier(appDomainEventBus, objectMapperSupplier.get()); + DiscardNotifier notifier = new DLQDiscardNotifier(appDomainEventBus, converter); connectionManager.setDiscardNotifier(domain, notifier); }); return connectionManager; @@ -175,7 +175,7 @@ public ObjectMapperSupplier objectMapperSupplier() { @Bean @ConditionalOnMissingBean public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new JacksonCloudEventMessageConverter(objectMapperSupplier.get()); + return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); } @Bean diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java index 67e20c5f..97f04e77 100644 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java +++ b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java @@ -8,7 +8,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; @@ -21,8 +21,6 @@ import reactor.rabbitmq.ExchangeSpecification; import reactor.rabbitmq.Receiver; -import java.lang.reflect.Field; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java index ec27f5e4..2a77857c 100644 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java +++ b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java @@ -7,7 +7,7 @@ import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java index 45d494ca..e019c4b4 100644 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java +++ b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java @@ -7,7 +7,7 @@ import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java index 13a2f2a4..d4a61131 100644 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java +++ b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; diff --git a/async/async-rabbit-starter/async-commons-rabbit-starter.gradle b/async/async-rabbit-starter/async-commons-rabbit-starter.gradle index 1f5b77ae..d2b89d6c 100644 --- a/async/async-rabbit-starter/async-commons-rabbit-starter.gradle +++ b/async/async-rabbit-starter/async-commons-rabbit-starter.gradle @@ -5,6 +5,8 @@ ext { dependencies { api project(':async-rabbit') + implementation 'io.projectreactor.rabbitmq:reactor-rabbitmq' + compileOnly 'org.springframework.boot:spring-boot-starter' compileOnly 'org.springframework.boot:spring-boot-starter-actuator' diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java index 49fe308c..e5db6229 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java +++ b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java @@ -2,10 +2,10 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; 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.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.config.props.AsyncProps; import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java index 6a528b5c..2cd3477f 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java +++ b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java @@ -2,10 +2,10 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; 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.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.config.props.AsyncProps; import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java index fe41588e..265f5b32 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java +++ b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java @@ -2,15 +2,13 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; 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.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java index cb2391ec..fa01ab6d 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java +++ b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java @@ -2,10 +2,10 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; 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.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.config.props.AsyncProps; diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java index 697ad7d8..e63771bd 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java @@ -4,35 +4,30 @@ import com.rabbitmq.client.ConnectionFactory; import lombok.RequiredArgsConstructor; 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.api.handlers.registered.RegisteredCommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; -import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.communications.Message; +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.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.commons.utils.resolver.HandlerResolverUtil; import org.reactivecommons.async.rabbit.DynamicRegistryImp; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.RabbitDiscardNotifier; +import org.reactivecommons.async.commons.DLQDiscardNotifier; 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.BrokerConfigProps; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -57,12 +52,8 @@ import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; -import java.util.stream.Stream; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; import static reactor.rabbitmq.ExchangeSpecification.exchange; @Log @@ -156,35 +147,20 @@ public ObjectMapperSupplier objectMapperSupplier() { @Bean @ConditionalOnMissingBean public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new JacksonMessageConverter(objectMapperSupplier.get()); + return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); } @Bean @ConditionalOnMissingBean - public DiscardNotifier rabbitDiscardNotifier(ObjectMapperSupplier objectMapperSupplier, AsyncProps asyncProps, + public DiscardNotifier rabbitDiscardNotifier(MessageConverter messageConverter, AsyncProps asyncProps, ReactiveMessageSender sender, BrokerConfigProps props) { - return new RabbitDiscardNotifier(domainEventBus(sender, props, asyncProps.getCreateTopology()), objectMapperSupplier.get()); + return new DLQDiscardNotifier(domainEventBus(sender, props, asyncProps.getCreateTopology()), messageConverter); } @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(); - } - }; + return new DefaultCustomReporter(); } private DomainEventBus domainEventBus(ReactiveMessageSender sender, BrokerConfigProps props, boolean createExchange) { @@ -208,47 +184,7 @@ Mono createConnectionMono(ConnectionFactory factory, String connecti @Bean public HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { final Map registries = context.getBeansOfType(HandlerRegistry.class); - - final ConcurrentMap> queryHandlers = registries - .values().stream() - .flatMap(r -> r.getHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventsToBind = registries - .values().stream() - .flatMap(r -> r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - // event handlers and dynamic handlers - final ConcurrentMap> eventHandlers = registries - .values().stream() - .flatMap(r -> Stream.concat(r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream(), r.getDynamicEventHandlers().stream())) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> commandHandlers = registries - .values().stream() - .flatMap(r -> r.getCommandHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventNotificationListener = registries - .values() - .stream() - .flatMap(r -> r.getEventNotificationListener().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, commandHandlers) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); - } - }; + return HandlerResolverUtil.fromHandlerRegistries(registries.values(), defaultCommandHandler); } @Bean diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java index 9ec575ab..d171d1a5 100644 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java +++ b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java @@ -10,7 +10,7 @@ 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.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java index ff39891f..a939a666 100644 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java +++ b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java @@ -8,7 +8,7 @@ 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.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java index a35c7b5a..ef070444 100644 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java +++ b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java @@ -8,7 +8,7 @@ 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.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java index 61c0a1d4..4806296a 100644 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java +++ b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java @@ -8,7 +8,7 @@ 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.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; diff --git a/async/async-rabbit/async-rabbit.gradle b/async/async-rabbit/async-rabbit.gradle index 1d9df2b6..db5ddce6 100644 --- a/async/async-rabbit/async-rabbit.gradle +++ b/async/async-rabbit/async-rabbit.gradle @@ -10,7 +10,7 @@ dependencies { api 'io.projectreactor:reactor-core' api 'io.projectreactor:reactor-core-micrometer' - api 'io.projectreactor.rabbitmq:reactor-rabbitmq' + api 'io.projectreactor.rabbitmq:reactor-rabbitmq:1.5.6' api 'com.rabbitmq:amqp-client' api 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'io.projectreactor:reactor-test' diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java index ab1e93d7..608cc099 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java @@ -3,11 +3,13 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.DynamicRegistry; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.config.IBrokerConfigProps; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Mono; @@ -22,7 +24,7 @@ public class DynamicRegistryImp implements DynamicRegistry { @Override - public Mono listenEvent(String eventName, EventHandler fn, Class eventClass) { + public Mono listenEvent(String eventName, DomainEventHandler fn, Class eventClass) { resolver.addEventListener(new RegisteredEventListener<>(eventName, fn, eventClass)); return topologyCreator.bind(buildBindingSpecification(eventName)) diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java index afb5e4da..15feb971 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java @@ -14,6 +14,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.OutboundMessageResult; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; import java.time.Duration; import java.util.Collections; @@ -21,6 +23,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeoutException; +import java.util.function.Function; import static java.lang.Boolean.TRUE; import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @@ -29,7 +32,6 @@ import static org.reactivecommons.async.commons.Headers.REPLY_ID; import static org.reactivecommons.async.commons.Headers.REPLY_TIMEOUT_MILLIS; import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; -import static reactor.core.publisher.Mono.fromCallable; public class RabbitDirectAsyncGateway implements DirectAsyncGateway { @@ -74,23 +76,31 @@ public Mono sendCommand(Command command, String targetName, String @Override public Mono sendCommand(Command command, String targetName, long delayMillis, String domain) { - Map headers = new HashMap<>(); - String realTarget = targetName; - if (delayMillis > 0) { - headers.put(DELAYED, String.valueOf(delayMillis)); - realTarget = targetName + "-delayed"; - } - return resolveSender(domain).sendWithConfirm(command, exchange, realTarget, headers, persistentCommands); + Tuple2> targetAndHeaders = validateDelay(targetName, delayMillis); + return resolveSender(domain).sendWithConfirm(command, exchange, targetAndHeaders.getT1(), + targetAndHeaders.getT2(), persistentCommands); } @Override public Mono sendCommand(CloudEvent command, String targetName) { - return sendCommand(new Command<>(command.getType(), command.getId(), command), targetName); + return sendCommand(command, targetName, 0, 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 sendCommand(new Command<>(command.getType(), command.getId(), command), targetName, domain); + return sendCommand(command, targetName, 0, domain); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain) { + Tuple2> targetAndHeaders = validateDelay(targetName, delayMillis); + return resolveSender(domain).sendWithConfirm(command, exchange, targetAndHeaders.getT1(), + targetAndHeaders.getT2(), persistentCommands); } public Flux sendCommands(Flux> commands, String targetName) { @@ -105,37 +115,41 @@ public Mono requestReply(AsyncQuery query, String targetName, Class @Override public Mono requestReply(AsyncQuery query, String targetName, Class type, String domain) { + return requestReplyInternal(query, targetName, type, domain, AsyncQuery::getResource); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type) { + return requestReplyInternal(query, targetName, type, DEFAULT_DOMAIN, CloudEvent::getType); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { + return requestReplyInternal(query, targetName, type, domain, CloudEvent::getType); + } + + private Mono requestReplyInternal(T query, String targetName, Class type, String domain, Function queryTypeExtractor) { final String correlationID = UUID.randomUUID().toString().replaceAll("-", ""); final Mono replyHolder = router.register(correlationID) .timeout(replyTimeout) .doOnError(TimeoutException.class, e -> router.deregister(correlationID)) - .flatMap(s -> fromCallable(() -> converter.readValue(s, type))); + .map(s -> converter.readValue(s, type)); Map headers = new HashMap<>(); headers.put(REPLY_ID, config.getRoutingKey()); - headers.put(SERVED_QUERY_ID, query.getResource()); + headers.put(SERVED_QUERY_ID, queryTypeExtractor.apply(query)); headers.put(CORRELATION_ID, correlationID); headers.put(REPLY_TIMEOUT_MILLIS, replyTimeout.toMillis()); return resolveSender(domain).sendNoConfirm(query, exchange, targetName + ".query", headers, persistentQueries) .then(replyHolder) .name("async_query") - .tag("operation", query.getResource()) + .tag("operation", queryTypeExtractor.apply(query)) .tag("target", targetName) .tap(Micrometer.metrics(meterRegistry)); } - @Override - public Mono requestReply(CloudEvent query, String targetName, Class type) { - return requestReply(new AsyncQuery<>(query.getType(), query), targetName, type); - } - - @Override - public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { - return requestReply(new AsyncQuery<>(query.getType(), query), targetName, type, domain); - } - @Override public Mono reply(T response, From from) { final HashMap headers = new HashMap<>(); @@ -152,4 +166,14 @@ protected ReactiveMessageSender resolveSender(String domain) { // NOSONAR return sender; } + private Tuple2> validateDelay(String targetName, long delayMillis) { + Map headers = new HashMap<>(); + String realTarget = targetName; + if (delayMillis > 0) { + headers.put(DELAYED, String.valueOf(delayMillis)); + realTarget = targetName + "-delayed"; + } + return Tuples.of(realTarget, headers); + } + } 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 57f99a9d..bb249453 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 @@ -33,8 +33,10 @@ public Mono emit(DomainEvent event) { } @Override - public Publisher emit(CloudEvent event) { - return emit(new DomainEvent<>(event.getType(), event.getId(), event)); + public Publisher emit(CloudEvent cloudEvent) { + return sender.sendWithConfirm(cloudEvent, exchange, cloudEvent.getType(), + Collections.emptyMap(), persistentEvents) + .onErrorMap(err -> new RuntimeException("Event send failure: " + cloudEvent.getType(), err)); } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java index 9dcdfc8c..f14446d7 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java @@ -13,19 +13,23 @@ public class RabbitMessage implements Message { private final Properties properties; @Data - public static class RabbitMessageProperties implements Properties{ + public static class RabbitMessageProperties implements Properties { private String contentType; private String contentEncoding; + private long timestamp; private long contentLength; private Map headers = new HashMap<>(); } - public static RabbitMessage fromDelivery(Delivery delivery){ + public static RabbitMessage fromDelivery(Delivery delivery) { return new RabbitMessage(delivery.getBody(), createMessageProps(delivery)); } private static Message.Properties createMessageProps(Delivery msj) { final RabbitMessage.RabbitMessageProperties properties = new RabbitMessage.RabbitMessageProperties(); + if (msj.getProperties().getTimestamp() != null) { + properties.setTimestamp(msj.getProperties().getTimestamp().getTime()); + } properties.setHeaders(msj.getProperties().getHeaders()); properties.setContentType(msj.getProperties().getContentType()); properties.setContentEncoding(msj.getProperties().getContentEncoding()); diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java index 51bfdacb..eb35f00c 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java @@ -49,7 +49,8 @@ public ReactiveMessageSender(Sender sender, String sourceApplication, MessageCon for (int i = 0; i < numberOfSenderSubscriptions; ++i) { final Flux messageSource = Flux.create(fluxSinkConfirm::add); - sender.sendWithTypedPublishConfirms(messageSource).doOnNext((OutboundMessageResult outboundMessageResult) -> { + sender.sendWithTypedPublishConfirms(messageSource) + .doOnNext((OutboundMessageResult outboundMessageResult) -> { final Consumer ackNotifier = outboundMessageResult.getOutboundMessage().getAckNotifier(); executorService.submit(() -> ackNotifier.accept(outboundMessageResult.isAck())); }).subscribe(); diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java deleted file mode 100644 index b4791e53..00000000 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.reactivecommons.async.rabbit.converters.json; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.cloudevents.CloudEvent; -import io.cloudevents.core.provider.EventFormatProvider; -import io.cloudevents.jackson.JsonFormat; -import lombok.Data; -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.converters.MessageConverter; -import org.reactivecommons.async.commons.exceptions.MessageConversionException; -import org.reactivecommons.async.rabbit.RabbitMessage; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -public class JacksonCloudEventMessageConverter implements MessageConverter { - private static final String CONTENT_TYPE = "application/json"; - public static final String FAILED_TO_CONVERT_MESSAGE_CONTENT = "Failed to convert Message content"; - - private final ObjectMapper objectMapper; - - - public JacksonCloudEventMessageConverter(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } - - @Override - public AsyncQuery readAsyncQuery(Message message, Class bodyClass) { - try { - final AsyncQueryJson asyncQueryJson = readValue(message, AsyncQueryJson.class); - T value = extractData(bodyClass, asyncQueryJson.getQueryData()); - return new AsyncQuery<>(asyncQueryJson.getResource(), value); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - public DomainEvent readDomainEvent(Message message, Class bodyClass) { - try { - final DomainEventJson domainEventJson = readValue(message, DomainEventJson.class); - - T value = extractData(bodyClass, domainEventJson.getData()); - - return new DomainEvent<>(domainEventJson.getName(), domainEventJson.getEventId(), value); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - public Command readCommand(Message message, Class bodyClass) { - try { - final CommandJson commandJson = readValue(message, CommandJson.class); - T value = extractData(bodyClass, commandJson.getData()); - return new Command<>(commandJson.getName(), commandJson.getCommandId(), value); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - @SuppressWarnings("unchecked") - public T readValue(Message message, Class valueClass) { - try { - if(valueClass == CloudEvent.class){ - - return (T) EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .deserialize(objectMapper.readValue(message.getBody(), byte[].class)); - - } - return objectMapper.readValue(message.getBody(), valueClass); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - @SuppressWarnings("unchecked") - public Command readCommandStructure(Message message) { - final CommandJson commandJson = readValue(message, CommandJson.class); - return new Command<>(commandJson.getName(), commandJson.getCommandId(), (T) commandJson.getData()); - } - - @Override - @SuppressWarnings("unchecked") - public DomainEvent readDomainEventStructure(Message message) { - final DomainEventJson eventJson = readValue(message, DomainEventJson.class); - return new DomainEvent<>(eventJson.getName(), eventJson.getEventId(), (T) eventJson.getData()); - } - - @Override - @SuppressWarnings("unchecked") - public AsyncQuery readAsyncQueryStructure(Message message) { - final AsyncQueryJson asyncQueryJson = readValue(message, AsyncQueryJson.class); - return new AsyncQuery<>(asyncQueryJson.getResource(), (T) asyncQueryJson.getQueryData()); - } - - - public Message commandToMessage(Command object) { - byte[] data = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize(object.getData()); - - return getRabbitMessage(new Command<>(object.getName(), object.getCommandId(), data)); - } - - - public Message eventToMessage(DomainEvent object) { - byte[] data = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize(object.getData()); - - return getRabbitMessage(new DomainEvent<>(object.getName(), object.getEventId(), data)); - } - - - public Message queryToMessage(AsyncQuery object) { - byte[] data = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize(object.getQueryData()); - - return getRabbitMessage(new AsyncQuery<>(object.getResource(), data)); - } - @Override - public Message toMessage(Object object) { - if(object instanceof DomainEvent - && ((DomainEvent) object).getData() instanceof CloudEvent){ - return eventToMessage((DomainEvent) object); - - } - if(object instanceof Command - && ((Command) object).getData() instanceof CloudEvent){ - return commandToMessage((Command) object); - } - if(object instanceof AsyncQuery - && ((AsyncQuery) object).getQueryData() instanceof CloudEvent){ - return queryToMessage((AsyncQuery) object); - } - return getRabbitMessage(object); - } - - private RabbitMessage getRabbitMessage(Object object) { - byte[] bytes; - try { - String jsonString = this.objectMapper.writeValueAsString(object); - bytes = jsonString.getBytes(StandardCharsets.UTF_8); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - RabbitMessage.RabbitMessageProperties props = new RabbitMessage.RabbitMessageProperties(); - props.setContentType(CONTENT_TYPE); - props.setContentEncoding(StandardCharsets.UTF_8.name()); - props.setContentLength(bytes.length); - return new RabbitMessage(bytes, props); - } - - private T extractData(Class bodyClass, JsonNode node) throws JsonProcessingException { - T value; - if(bodyClass == CloudEvent.class){ - - value = (T) EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .deserialize(Base64.getDecoder() - .decode(node.asText())); - - } - else{ - value = objectMapper.treeToValue(node, bodyClass); - } - return value; - } - - @Data - private static class AsyncQueryJson { - private String resource; - private JsonNode queryData; - } - - @Data - private static class DomainEventJson { - private String name; - private String eventId; - private JsonNode data; - } - - @Data - private static class CommandJson { - private String name; - private String commandId; - private JsonNode data; - } -} diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java new file mode 100644 index 00000000..db19e9fb --- /dev/null +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java @@ -0,0 +1,38 @@ +package org.reactivecommons.async.rabbit.converters.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.commons.exceptions.MessageConversionException; +import org.reactivecommons.async.rabbit.RabbitMessage; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class RabbitJacksonMessageConverter extends JacksonMessageConverter { + + public RabbitJacksonMessageConverter(ObjectMapper objectMapper) { + super(objectMapper); + } + + @Override + public Message toMessage(Object object) { + byte[] bytes; + try { + String jsonString = this.objectMapper.writeValueAsString(object); + bytes = jsonString.getBytes(StandardCharsets.UTF_8); + } catch (IOException e) { + throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); + } + RabbitMessage.RabbitMessageProperties props = new RabbitMessage.RabbitMessageProperties(); + if (object instanceof CloudEvent) { + props.setContentType(APPLICATION_CLOUD_EVENT_JSON); + } else { + props.setContentType(CONTENT_TYPE); + } + props.setContentEncoding(StandardCharsets.UTF_8.name()); + props.setContentLength(bytes.length); + return new RabbitMessage(bytes, props); + } +} diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java index 64d48bac..70af3dc3 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java @@ -1,15 +1,17 @@ package org.reactivecommons.async.rabbit.listeners; +import com.fasterxml.jackson.databind.JsonNode; import com.rabbitmq.client.AMQP; import lombok.extern.java.Log; -import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.handlers.CloudCommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.commons.CommandExecutor; import org.reactivecommons.async.commons.DiscardNotifier; 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.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.RabbitMessage; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; @@ -24,6 +26,10 @@ @Log public class ApplicationCommandListener extends GenericMessageListener { + private static final String DQL = ".DLQ"; + private static final String NAME = "name"; + private static final String COMMAND_ID = "commandId"; + private static final String TYPE = "type"; private final MessageConverter messageConverter; private final HandlerResolver resolver; private final String directExchange; @@ -60,11 +66,11 @@ public ApplicationCommandListener(ReactiveMessageListener listener, protected Mono setUpBindings(TopologyCreator creator) { final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(directExchange).durable(true).type("direct")); if (withDLQRetry) { - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(directExchange + ".DLQ").durable(true).type("direct")); - final Mono declareQueue = creator.declareQueue(queueName, directExchange + ".DLQ", maxLengthBytes); + final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(directExchange + DQL).durable(true).type("direct")); + final Mono declareQueue = creator.declareQueue(queueName, directExchange + DQL, maxLengthBytes); final Mono declareDLQ = creator.declareDLQ(queueName, directExchange, retryDelay, maxLengthBytes); final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(directExchange + ".DLQ", queueName, queueName + ".DLQ")); + final Mono bindingDLQ = creator.bind(BindingSpecification.binding(directExchange + DQL, queueName, queueName + DQL)); return declareExchange.then(declareExchangeDLQ) .then(declareDLQ) .then(declareQueue) @@ -92,16 +98,20 @@ private Mono declareDelayedTopology(TopologyCreator creator) { @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredCommandHandler handler = resolver.getCommandHandler(executorPath); - final Class eventClass = handler.getInputClass(); - Function> converter = msj -> messageConverter.readCommand(msj, eventClass); + final RegisteredCommandHandler handler = resolver.getCommandHandler(executorPath); + Function converter = resolveConverter(handler); final CommandExecutor executor = new CommandExecutor<>(handler.getHandler(), converter); return msj -> executor.execute(msj).cast(Object.class); } protected String getExecutorPath(AcknowledgableDelivery msj) { - final Command command = messageConverter.readCommandStructure(RabbitMessage.fromDelivery(msj)); - return command.getName(); + RabbitMessage rabbitMessage = RabbitMessage.fromDelivery(msj); + JsonNode jsonNode = messageConverter.readValue(rabbitMessage, JsonNode.class); + if (jsonNode.get(COMMAND_ID) != null) { + return jsonNode.get(NAME).asText(); + } else { + return jsonNode.get(TYPE).asText(); + } } @Override @@ -109,6 +119,15 @@ protected Object parseMessageForReporter(Message message) { return messageConverter.readCommandStructure(message); } + private Function resolveConverter(RegisteredCommandHandler registeredCommandHandler) { + if (registeredCommandHandler.getHandler() instanceof DomainCommandHandler) { + final Class commandClass = registeredCommandHandler.getInputClass(); + return msj -> messageConverter.readCommand(msj, commandClass); + } else if (registeredCommandHandler.getHandler() instanceof CloudCommandHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java index d57edc12..73f1e481 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java @@ -2,14 +2,15 @@ import com.rabbitmq.client.AMQP; import lombok.extern.java.Log; -import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; 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.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Flux; @@ -81,11 +82,9 @@ protected Mono setUpBindings(TopologyCreator creator) { @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredEventListener handler = resolver.getEventListener(executorPath); - - final Class eventClass = handler.getInputClass(); - Function> converter = msj -> messageConverter.readDomainEvent(msj, eventClass); + final RegisteredEventListener handler = resolver.getEventListener(executorPath); + Function converter = resolveConverter(handler); final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); return msj -> executor @@ -102,6 +101,16 @@ protected Object parseMessageForReporter(Message msj) { return messageConverter.readDomainEventStructure(msj); } + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } } 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 cd01c6b2..c5e204b5 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 @@ -2,14 +2,15 @@ import com.rabbitmq.client.AMQP; import lombok.extern.java.Log; -import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; 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.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; +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; @@ -77,10 +78,11 @@ protected Mono setUpBindings(TopologyCreator creator) { @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredEventListener eventListener = resolver.getNotificationListener(executorPath); - final Function> converter = message -> messageConverter - .readDomainEvent(message, eventListener.getInputClass()); + final RegisteredEventListener eventListener = resolver.getNotificationListener(executorPath); + + Function converter = resolveConverter(eventListener); final EventExecutor executor = new EventExecutor<>(eventListener.getHandler(), converter); + return message -> executor .execute(message) .cast(Object.class); @@ -96,4 +98,16 @@ protected String getExecutorPath(AcknowledgableDelivery message) { protected Object parseMessageForReporter(Message msj) { return messageConverter.readDomainEventStructure(msj); } + + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } + } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java index 978f2ef8..59b40f6a 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java @@ -7,11 +7,11 @@ import lombok.extern.java.Log; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.QueryExecutor; 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.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; @@ -71,12 +71,18 @@ protected Function> rawMessageHandler(String executorPath) if (handler == null) { return message -> Mono.error(new RuntimeException("Handler Not registered for Query: " + executorPath)); } - final Class handlerClass = handler.getQueryClass(); - Function messageConverter = msj -> converter.readAsyncQuery(msj, handlerClass).getQueryData(); + Function messageConverter = resolveConverter(handler.getQueryClass()); final QueryExecutor executor = new QueryExecutor<>(handler.getHandler(), messageConverter); return executor::execute; } + private Function resolveConverter(Class handlerClass) { + if (handlerClass == CloudEvent.class) { + return converter::readCloudEvent; + } + return msj -> converter.readAsyncQuery(msj, handlerClass).getQueryData(); + } + protected Mono setUpBindings(TopologyCreator creator) { final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(directExchange).durable(true).type("direct")); if (withDLQRetry) { @@ -152,14 +158,6 @@ protected Function, Mono> enrichPostProcess(Message msg) { final HashMap headers = new HashMap<>(); headers.put(CORRELATION_ID, correlationID); Object response = signal.get(); - if (response instanceof CloudEvent) { - byte[] serialized = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize((CloudEvent) response); - return sender.sendNoConfirm(serialized, replyExchange, replyID, headers, false); - } - return sender.sendNoConfirm(response, replyExchange, replyID, headers, false); }); } 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 1fd937c2..a6555431 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 @@ -103,9 +103,13 @@ public void startListener() { onTerminate(); } - private void onTerminate() { - messageFlux.doOnTerminate(this::onTerminate) - .subscribe(new LoggerSubscriber<>(getClass().getName())); + private Flux consumeFaultTolerant(Flux messageFlux) { + return messageFlux.flatMap(msj -> { + final Instant init = Instant.now(); + return handle(msj, init) + .doOnSuccess(AcknowledgableDelivery::ack) + .onErrorResume(err -> requeueOrAck(msj, err, init)); + }, messageListener.getMaxConcurrency()); } protected Mono handle(AcknowledgableDelivery msj, Instant initTime) { @@ -134,6 +138,11 @@ protected Mono handle(AcknowledgableDelivery msj, Instan } } + private void onTerminate() { + messageFlux.doOnTerminate(this::onTerminate) + .subscribe(new LoggerSubscriber<>(getClass().getName())); + } + private void logExecution(String executorPath, Instant initTime, boolean success) { try { final Instant afterExecutionTime = Instant.now(); @@ -161,15 +170,6 @@ private void doLogExecution(String executorPath, long timeElapsed) { objectType, executorPath, timeElapsed)); } - private Flux consumeFaultTolerant(Flux messageFlux) { - return messageFlux.flatMap(msj -> { - final Instant init = Instant.now(); - return handle(msj, init) - .doOnSuccess(AcknowledgableDelivery::ack) - .onErrorResume(err -> requeueOrAck(msj, err, init)); - }, messageListener.getMaxConcurrency()); - } - protected void logError(Throwable err, AcknowledgableDelivery msj, FallbackStrategy strategy) { String messageID = msj.getProperties().getMessageId(); @@ -185,14 +185,7 @@ protected void logError(Throwable err, AcknowledgableDelivery msj, FallbackStrat } private Function> getExecutor(String path) { - final Function> handler = handlers.get(path); - return handler != null ? handler : computeRawMessageHandler(path); - } - - private Function> computeRawMessageHandler(String commandId) { - return handlers.computeIfAbsent(commandId, s -> - rawMessageHandler(commandId) - ); + return handlers.computeIfAbsent(path, this::rawMessageHandler); } protected abstract Function> rawMessageHandler(String executorPath); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java index b7b769cc..4ae881eb 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java @@ -11,6 +11,7 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.config.IBrokerConfigProps; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Mono; @@ -43,10 +44,10 @@ class DynamicRegistryImpTest { @BeforeEach @SuppressWarnings("rawtypes") void setUp() { - Map> commandHandlers = new ConcurrentHashMap<>(); - Map> eventListeners = new ConcurrentHashMap<>(); - Map> eventsToBind = new ConcurrentHashMap<>(); - Map> notificationEventListeners = new ConcurrentHashMap<>(); + Map> commandHandlers = new ConcurrentHashMap<>(); + Map> eventListeners = new ConcurrentHashMap<>(); + Map> eventsToBind = new ConcurrentHashMap<>(); + Map> notificationEventListeners = new ConcurrentHashMap<>(); Map> queryHandlers = new ConcurrentHashMap<>(); resolver = new HandlerResolver(queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers); dynamicRegistry = new DynamicRegistryImp(resolver, topologyCreator, props); @@ -58,7 +59,7 @@ void registerEventListener() { when(topologyCreator.bind(any())).thenReturn(just(mock(BindOk.class))); dynamicRegistry.listenEvent("event1", message -> Mono.empty(), Long.class); - final RegisteredEventListener listener = resolver.getEventListener("event1"); + final RegisteredEventListener listener = resolver.getEventListener("event1"); assertThat(listener).isNotNull(); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java index 0f696fa9..a5b2dd6e 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java @@ -6,6 +6,7 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import java.util.Collection; @@ -17,15 +18,15 @@ class HandlerResolverTest { @BeforeEach void setup() { - Map> commandHandlers = new ConcurrentHashMap<>(); - Map> eventListeners = new ConcurrentHashMap<>(); + Map> commandHandlers = new ConcurrentHashMap<>(); + Map> eventListeners = new ConcurrentHashMap<>(); eventListeners.put("event.name", new RegisteredEventListener<>("event.name", message -> Mono.empty(), String.class)); eventListeners.put("event.name2", new RegisteredEventListener<>("event.name2", message -> Mono.empty(), String.class)); eventListeners.put("some.*", new RegisteredEventListener<>("some.*", message -> Mono.empty(), String.class)); - Map> eventsToBind = new ConcurrentHashMap<>(); + Map> eventsToBind = new ConcurrentHashMap<>(); eventsToBind.put("event.name", new RegisteredEventListener<>("event.name", message -> Mono.empty(), String.class)); eventsToBind.put("event.name2", new RegisteredEventListener<>("event.name2", message -> Mono.empty(), String.class)); - Map> notificationEventListeners = new ConcurrentHashMap<>(); + Map> notificationEventListeners = new ConcurrentHashMap<>(); Map> queryHandlers = new ConcurrentHashMap<>(); resolver = new HandlerResolver(queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers); } @@ -33,7 +34,7 @@ void setup() { @Test void shouldGetOnlyTheBindingEvents() { // Act - Collection> eventListener = resolver.getEventListeners(); + Collection> eventListener = resolver.getEventListeners(); // Assert Assertions.assertThat(eventListener.size()).isEqualTo(2); } @@ -41,7 +42,7 @@ void shouldGetOnlyTheBindingEvents() { @Test void shouldMatchForAWildcardEvent() { // Act - RegisteredEventListener eventListener = resolver.getEventListener("some.sample"); + RegisteredEventListener eventListener = resolver.getEventListener("some.sample"); // Assert Assertions.assertThat(eventListener.getPath()).isEqualTo("some.*"); } @@ -49,7 +50,7 @@ void shouldMatchForAWildcardEvent() { @Test void shouldMatchForAnExactEvent() { // Act - RegisteredEventListener eventListener = resolver.getEventListener("event.name"); + RegisteredEventListener eventListener = resolver.getEventListener("event.name"); // Assert Assertions.assertThat(eventListener.getPath()).isEqualTo("event.name"); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java index 5522ad75..be1346bb 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java @@ -19,7 +19,8 @@ import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -59,7 +60,7 @@ class RabbitDirectAsyncGatewayTest { private final BrokerConfig config = new BrokerConfig(); private final Semaphore semaphore = new Semaphore(0); - private final MessageConverter converter = new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + private final MessageConverter converter = new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); @Mock private ReactiveReplyRouter router; @Mock diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java index 1c499905..1dd463d8 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java @@ -10,8 +10,7 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -38,7 +37,7 @@ class ReactiveMessageSenderTest { private Sender sender; @Spy - private final MessageConverter messageConverter = new JacksonMessageConverter(objectMapper); + private final MessageConverter messageConverter = new RabbitJacksonMessageConverter(objectMapper); @BeforeEach public void init() { diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java index 03335367..a12f0d37 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import java.util.Date; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java deleted file mode 100644 index 71da4ecc..00000000 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.reactivecommons.async.rabbit.converters.json; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.cloudevents.CloudEvent; -import io.cloudevents.core.builder.CloudEventBuilder; -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 java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.OffsetDateTime; -import java.util.Date; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; - -public class JacksonCloudEventMessageConverterTest { - - private final ObjectMapper objectMapper = new ObjectMapper(); - private final JacksonCloudEventMessageConverter converter = new JacksonCloudEventMessageConverter(objectMapper); - - @Test - void readAsyncQuery() throws JsonProcessingException { - Date date = new Date(); - CloudEvent query = CloudEventBuilder.v1() // - .withId(UUID.randomUUID().toString()) // - .withSource(URI.create("https://spring.io/foos"))// - .withType("query") - .withData("application/json", CloudEventBuilderExt.asBytes(new SampleClass("35", "name1", date))) - .build(); - final Message message = converter.toMessage(new AsyncQuery<>(query.getType(), query)); - final AsyncQuery value = converter.readAsyncQuery(message, CloudEvent.class); - byte[] bytes = value.getQueryData().getData().toBytes(); - String stringData = new String(bytes, StandardCharsets.UTF_8); - SampleClass result = objectMapper.readValue(stringData, SampleClass.class); - assertThat(result) - .extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - - } - - @Test - void readDomainEvent() throws JsonProcessingException { - Date date = new Date(); - CloudEvent event = CloudEventBuilder.v1() // - .withId(UUID.randomUUID().toString()) // - .withSource(URI.create("https://spring.io/foos"))// - .withType("event") - .withData("application/json", CloudEventBuilderExt.asBytes(new SampleClass("35", "name1", date))) - .build(); - final Message message = converter.toMessage(new DomainEvent<>(event.getType(), event.getId(), event)); - final DomainEvent value = converter.readDomainEvent(message, CloudEvent.class); - byte[] bytes = value.getData().getData().toBytes(); - String stringData = new String(bytes, StandardCharsets.UTF_8); - SampleClass result = objectMapper.readValue(stringData, SampleClass.class); - assertThat(result) - .extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - } - - @Test - void readCommand() throws JsonProcessingException { - Date date = new Date(); - CloudEvent command = CloudEventBuilder.v1() // - .withId(UUID.randomUUID().toString()) // - .withSource(URI.create("https://spring.io/foos"))// - .withType("command") - .withData("application/json", CloudEventBuilderExt.asBytes(new SampleClass("35", "name1", date))) - .build(); - final Message message = converter.toMessage(new Command<>(command.getType(), command.getId(), command)); - final Command value = converter.readCommand(message, CloudEvent.class); - byte[] bytes = value.getData().getData().toBytes(); - String stringData = new String(bytes, StandardCharsets.UTF_8); - SampleClass result = objectMapper.readValue(stringData, SampleClass.class); - assertThat(result) - .extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - } - @Test - void toMessage() { - final Message message = converter.toMessage(new SampleClass("42", "Daniel", new Date())); - assertThat(new String(message.getBody())).contains("42").contains("Daniel"); - } - - @Test - void toMessageWhenDataIsNull() throws IOException { - final Message message = converter.toMessage(null); - - final JsonNode jsonNode = objectMapper.readTree(message.getBody()); - assertThat(jsonNode.isNull()).isTrue(); - } - - @Test - void toMessageWhenDataIsEmpty() throws IOException { - final Message message = converter.toMessage(""); - - final JsonNode jsonNode = objectMapper.readTree(message.getBody()); - assertThat(jsonNode.asText()).isEmpty(); - } - - @Test - void readValue() { - Date date = new Date(); - final Message message = converter.toMessage(new SampleClass("35", "name1", date)); - final SampleClass value = converter.readValue(message, SampleClass.class); - assertThat(value).extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - } - - @Test - void readValueString() { - final Message message = converter.toMessage("Hi!"); - final String value = converter.readValue(message, String.class); - assertThat(value).isEqualTo("Hi!"); - } - - @Test - void shouldConvertToCommandStructure() { - final SampleClass data = new SampleClass("35", "name1", new Date()); - final Message message = converter.toMessage(new Command<>("cmd.name", "42", data)); - final Command command = converter.readCommandStructure(message); - - assertThat(command.getData()).isInstanceOf(JsonNode.class); - assertThat(command.getName()).isEqualTo("cmd.name"); - } - - @Test - void shouldConvertToDomainEventStructure() { - final SampleClass data = new SampleClass("35", "name1", new Date()); - final Message message = converter.toMessage(new DomainEvent<>("event.name", "42", data)); - final DomainEvent event = converter.readDomainEventStructure(message); - - assertThat(event.getData()).isInstanceOf(JsonNode.class); - assertThat(event.getName()).isEqualTo("event.name"); - final JsonNode jsonNode = (JsonNode) event.getData(); - assertThat(jsonNode.findValue("name").asText()).isEqualTo("name1"); - } - - @Test - void shouldConvertToQueryStructure() { - final SampleClass data = new SampleClass("35", "sample1", new Date()); - final Message message = converter.toMessage(new AsyncQuery<>("query.name", data)); - final AsyncQuery query = converter.readAsyncQueryStructure(message); - - assertThat(query.getQueryData()).isInstanceOf(JsonNode.class); - assertThat(query.getResource()).isEqualTo("query.name"); - final JsonNode jsonNode = (JsonNode) query.getQueryData(); - assertThat(jsonNode.findValue("name").asText()).isEqualTo("sample1"); - } - - @Test - void shouldNotFailWithTilde() { - // Arrange - final String name = "example with word containing tilde áéíóúñ"; - final SampleClass data = new SampleClass("35", name, new Date()); - final Message message = converter.toMessage(new AsyncQuery<>("query.name", data)); - // Act - final AsyncQuery query = converter.readAsyncQueryStructure(message); - // Assert - assertThat(query.getQueryData()).isInstanceOf(JsonNode.class); - assertThat(query.getResource()).isEqualTo("query.name"); - final JsonNode jsonNode = (JsonNode) query.getQueryData(); - assertThat(jsonNode.findValue("name").asText()).isEqualTo(name); - } -} diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java index bae8220e..fb272ab4 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java @@ -1,22 +1,37 @@ package org.reactivecommons.async.rabbit.converters.json; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.jackson.JsonCloudEventData; +import org.junit.jupiter.api.BeforeAll; 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.converters.json.DefaultObjectMapperSupplier; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; import java.io.IOException; +import java.net.URI; import java.util.Date; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; class JacksonMessageConverterTest { - private final ObjectMapper objectMapper = new ObjectMapper(); - private final JacksonMessageConverter converter = new JacksonMessageConverter(objectMapper); + private static RabbitJacksonMessageConverter converter; + private static ObjectMapper objectMapper; + + @BeforeAll + static void setUp() { + objectMapper = new DefaultObjectMapperSupplier().get(); + converter = new RabbitJacksonMessageConverter(objectMapper); + } @Test void toMessage() { @@ -49,6 +64,21 @@ void readValue() { .containsExactly("35", "name1", date); } + @Test + void readCloudEvent() throws JsonProcessingException { + Date date = new Date(); + CloudEvent command = CloudEventBuilder.v1() // + .withId(UUID.randomUUID().toString()) // + .withSource(URI.create("https://spring.io/foos"))// + .withType("command") + .withData("application/json", JsonCloudEventData.wrap(objectMapper.valueToTree(new SampleClass("35", "name1", date)))) + .build(); + Message message = converter.toMessage(command); + CloudEvent result = converter.readCloudEvent(message); + + assertThat(result).usingRecursiveComparison().isEqualTo(command); + } + @Test void readValueString() { final Message message = converter.toMessage("Hi!"); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java index 151c4809..705c3da8 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java @@ -1,14 +1,16 @@ package org.reactivecommons.async.rabbit.converters.json; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; -@RequiredArgsConstructor -@Getter +@Data +@AllArgsConstructor +@NoArgsConstructor class SampleClass { - private final String id; - private final String name; - private final Date date; + private String id; + private String name; + private Date date; } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java index f3802467..672cf727 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java @@ -1,10 +1,11 @@ -package org.reactivecommons.async.commons.ext; +package org.reactivecommons.async.rabbit.ext; 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 reactor.test.publisher.PublisherProbe; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java index ba05c195..c8df1070 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java @@ -10,64 +10,70 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DefaultCommandHandler; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivecommons.async.utils.TestUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.AcknowledgableDelivery; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ExchangeSpecification; import reactor.rabbitmq.Receiver; import java.math.BigInteger; import java.time.Duration; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static reactor.core.publisher.Flux.range; -import static reactor.core.publisher.Mono.just; @ExtendWith(MockitoExtension.class) class ApplicationCommandListenerPerfTest { + private static final CountDownLatch latch = new CountDownLatch(12 + 1); + private static final int messageCount = 40000; + private final Semaphore semaphore = new Semaphore(0); @Mock private Receiver receiver; - @Mock private TopologyCreator topologyCreator; - @Mock private DiscardNotifier discardNotifier; - @Mock private CustomReporter errorReporter; - private StubGenericMessageListener messageListener; - private static final CountDownLatch latch = new CountDownLatch(12 + 1); - - private static final int messageCount = 40000; - private final Semaphore semaphore = new Semaphore(0); - private MessageConverter messageConverter = new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + private MessageConverter messageConverter = new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); private ReactiveMessageListener reactiveMessageListener; + private static BigInteger makeHardWork() { + final long number = ThreadLocalRandom.current().nextLong(100) + 2700; + BigInteger fact = new BigInteger("1"); + for (long i = 1; i <= number; i++) { + fact = fact.multiply(BigInteger.valueOf(i)); + } + return fact; + } + @BeforeEach public void setUp() { reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); @@ -169,15 +175,6 @@ private void liveLock(int delay) { for (long end = System.currentTimeMillis() + delay; System.currentTimeMillis() < end; ) ; } - private static BigInteger makeHardWork() { - final long number = ThreadLocalRandom.current().nextLong(100) + 2700; - BigInteger fact = new BigInteger("1"); - for (long i = 1; i <= number; i++) { - fact = fact.multiply(BigInteger.valueOf(i)); - } - return fact; - } - @Test void shouldProcessCPUMessagesInParallel() throws JsonProcessingException, InterruptedException { @@ -265,14 +262,14 @@ void shouldProcessPasiveBlockingMessagesInParallel() throws JsonProcessingExcept private HandlerResolver createHandlerResolver(final HandlerRegistry initialRegistry) { final HandlerRegistry registry = range(0, 20).reduce(initialRegistry, (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); - final ConcurrentMap> commandHandlers = registry.getCommandHandlers().stream() + final ConcurrentMap> commandHandlers = registry.getCommandHandlers().stream() .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); return new HandlerResolver(null, null, null, null, commandHandlers) { @Override @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler("", new DefaultCommandHandler() { + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = (RegisteredCommandHandler) super.getCommandHandler(path); + return handler != null ? handler : new RegisteredCommandHandler>("", new DefaultCommandHandler() { @Override public Mono handle(Command message) { return Mono.error(new RuntimeException("Default handler in Test")); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java index cea00632..4b74e5a6 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java @@ -5,7 +5,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import java.util.Optional; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java index 4733aa4e..62daa8a1 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java @@ -5,7 +5,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import java.util.Optional; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java index 2265b55a..8b146860 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java @@ -8,7 +8,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import reactor.rabbitmq.QueueSpecification; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java index a1cc33bc..b5ec8c32 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java @@ -5,7 +5,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import reactor.core.publisher.Mono; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java index 70b4a88e..a4e39f2d 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java @@ -8,29 +8,27 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; +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.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.ext.CustomReporter; import org.reactivecommons.async.helpers.SampleClass; import org.reactivecommons.async.helpers.TestStubs; -import org.reactivecommons.async.rabbit.HandlerResolver; 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.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import reactor.core.publisher.Mono; import reactor.rabbitmq.AcknowledgableDelivery; import reactor.rabbitmq.Receiver; import reactor.test.StepVerifier; -import reactor.test.publisher.PublisherProbe; import java.time.Instant; import java.util.Date; @@ -39,11 +37,23 @@ import java.util.Optional; import java.util.function.Function; -import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyMap; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.*; -import static org.reactivecommons.async.commons.Headers.*; -import static reactor.core.publisher.Mono.*; +import static org.reactivecommons.async.commons.Headers.CORRELATION_ID; +import static org.reactivecommons.async.commons.Headers.REPLY_ID; +import static org.reactivecommons.async.commons.Headers.REPLY_TIMEOUT_MILLIS; +import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; +import static reactor.core.publisher.Mono.empty; +import static reactor.core.publisher.Mono.error; +import static reactor.core.publisher.Mono.just; @ExtendWith(MockitoExtension.class) class ApplicationQueryListenerTest { @@ -51,7 +61,7 @@ class ApplicationQueryListenerTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final MessageConverter messageConverter = - new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); @Mock @@ -86,7 +96,7 @@ public void setUp() { HandlerResolver resolver = new HandlerResolver(handlers, null, null, null, null); applicationQueryListener = new ApplicationQueryListener(reactiveMessageListener, "queue", resolver, sender, "directExchange", messageConverter, "replyExchange", false, - true,1, 100, maxLengthBytes, true, discardNotifier, errorReporter); + true, 1, 100, maxLengthBytes, true, discardNotifier, errorReporter); } @Test diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java index 7e9b5ad2..b70a2196 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java @@ -13,15 +13,15 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.Headers; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivecommons.async.utils.TestUtils; import reactor.core.publisher.Flux; import reactor.rabbitmq.AcknowledgableDelivery; @@ -56,18 +56,16 @@ public abstract class ListenerReporterTestSuperClass { - private final ObjectMapper mapper = new ObjectMapper(); - - private final Receiver receiver = mock(Receiver.class); protected final TopologyCreator topologyCreator = mock(TopologyCreator.class); protected final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - protected final MessageConverter messageConverter = new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + protected final MessageConverter messageConverter = new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); protected final CustomReporter errorReporter = mock(CustomReporter.class); - private GenericMessageListener messageListener; - protected final Semaphore semaphore = new Semaphore(0); protected final Semaphore successSemaphore = new Semaphore(0); + private final ObjectMapper mapper = new ObjectMapper(); + private final Receiver receiver = mock(Receiver.class); protected final ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); + private GenericMessageListener messageListener; @BeforeEach public void init() { @@ -132,11 +130,11 @@ protected Flux createSource(Function rout protected abstract GenericMessageListener createMessageListener(final HandlerResolver handlerResolver); private HandlerResolver createHandlerResolver(final HandlerRegistry registry) { - final Map> eventHandlers = Stream.concat(registry.getDynamicEventHandlers().stream(), registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()).collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> eventsToBind = registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> notificationHandlers = registry.getEventNotificationListener().stream().collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> eventHandlers = Stream.concat(registry.getDynamicEventHandlers().stream(), registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()).collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> eventsToBind = registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> notificationHandlers = registry.getEventNotificationListener().stream().collect(toMap(RegisteredEventListener::getPath, identity())); final Map> queryHandlers = registry.getHandlers().stream().collect(toMap(RegisteredQueryHandler::getPath, identity())); - final Map> commandHandlers = registry.getCommandHandlers().stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); + final Map> commandHandlers = registry.getCommandHandlers().stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); return new HandlerResolver( new ConcurrentHashMap<>(queryHandlers), new ConcurrentHashMap<>(eventHandlers), diff --git a/domain/domain-events/domain-events-api.gradle b/domain/domain-events/domain-events-api.gradle index 7af7f448..702f146a 100644 --- a/domain/domain-events/domain-events-api.gradle +++ b/domain/domain-events/domain-events-api.gradle @@ -6,4 +6,5 @@ ext { dependencies { api 'org.reactivestreams:reactive-streams:1.0.4' api 'io.cloudevents:cloudevents-api:4.0.1' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 689437dd..9064fd0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ version=4.1.4 -toPublish=async-commons,async-commons-api,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-commons-rabbit-starter-eda,domain-events-api,async-rabbit +toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter onlyUpdater=true \ No newline at end of file diff --git a/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java b/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java index 9c86cfa2..5f4807dd 100644 --- a/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java +++ b/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java @@ -3,6 +3,7 @@ import lombok.extern.log4j.Log4j2; import org.reactivecommons.async.api.DynamicRegistry; import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.EventHandler; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; @@ -18,7 +19,7 @@ @Configuration public class HandlersConfig { - private EventHandler animalEventEventHandler; + private DomainEventHandler animalEventEventHandler; @Bean @Primary diff --git a/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java b/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java index 676903cc..946cd642 100644 --- a/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java +++ b/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java @@ -6,7 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.rabbit.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import sample.model.Constants; diff --git a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java index f422b54b..3e5f951f 100644 --- a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java +++ b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java @@ -6,7 +6,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; -import org.reactivecommons.async.rabbit.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java index 1986bb9b..cd395029 100644 --- a/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java +++ b/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java @@ -6,7 +6,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; -import org.reactivecommons.async.rabbit.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; From 0c38252c3b734a144bf77397b3d110cd00b56c12 Mon Sep 17 00:00:00 2001 From: Juan C Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:56:52 -0500 Subject: [PATCH 05/50] build(release): Add prereleases --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a93eaa31..68c28a76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ name: release on: release: - types: [ released ] + types: [ released, prereleased ] jobs: release: runs-on: ubuntu-latest From 0233d1a4af65c55df29f969c7f84c9995ca099f1 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Thu, 8 Aug 2024 21:05:39 +0000 Subject: [PATCH 06/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71efa72a..b1712a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.0.0-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.0-alpha) (2024-08-08) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.4...v5.0.0-alpha) + +**Merged pull requests:** + +- chore\(next\): add native support for Cloud Events [\#115](https://github.com/reactive-commons/reactive-commons-java/pull/115) ([jcanacon](https://github.com/jcanacon)) + ## [v4.1.4](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.4) (2024-07-25) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.3...v4.1.4) diff --git a/gradle.properties b/gradle.properties index 9064fd0e..7a1b628c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=4.1.4 +version=5.0.0-alpha toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter onlyUpdater=true \ No newline at end of file From 7ced50c504ee832cc9e6ce40574783e46e7930dd Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:48:03 -0500 Subject: [PATCH 07/50] chore(kafka): Implement kafka binding with support for Domain Events (#116) chore(kafka): Implement kafka binding with support for Domain Events --------- Co-authored-by: AndresFelipe11 Co-authored-by: jespinosas --- .gitignore | 3 +- .../async/api/HandlerRegistryTest.java | 13 +- async/async-kafka/async-kafka.gradle | 12 + .../async/kafka/KafkaDomainEventBus.java | 23 ++ .../async/kafka/KafkaMessage.java | 52 ++++ .../ReactiveMessageListener.java | 30 +++ .../communications/ReactiveMessageSender.java | 91 +++++++ .../exceptions/TopicNotFoundException.java | 7 + .../topology/KafkaCustomizations.java | 26 ++ .../topology/TopicCustomization.java | 19 ++ .../topology/TopologyCreator.java | 76 ++++++ .../json/KafkaJacksonMessageConverter.java | 61 +++++ .../listeners/ApplicationEventListener.java | 75 ++++++ .../ApplicationNotificationsListener.java | 78 ++++++ .../listeners/GenericMessageListener.java | 233 ++++++++++++++++++ .../async/kafka/KafkaMessageTest.java | 57 +++++ .../topology/KafkaCustomizationsTest.java | 54 ++++ .../topology/TopologyCreatorTest.java | 96 ++++++++ .../KafkaJacksonMessageConverterTest.java | 88 +++++++ .../ApplicationEventListenerTest.java | 99 ++++++++ .../listeners/GenericMessageListenerTest.java | 125 ++++++++++ .../async/rabbit/config/RabbitMqConfig.java | 1 + .../rabbit/RabbitDirectAsyncGatewayTest.java | 1 - .../json/JacksonMessageConverterTest.java | 4 +- gradle.properties | 2 +- .../async-kafka-sender-client.gradle | 9 + .../main/java/sample/EDASampleSenderApp.java | 17 ++ .../src/main/java/sample/KafkaConfig.java | 26 ++ .../src/main/java/sample/ListenerConfig.java | 36 +++ .../java/sample/SampleRestController.java | 127 ++++++++++ .../src/main/resources/application.yaml | 17 ++ .../java/sample/SampleRestController.java | 1 - .../async-kafka-starter.gradle | 16 ++ .../annotations/EnableDomainEventBus.java | 23 ++ .../annotations/EnableEventListeners.java | 23 ++ .../EnableNotificationListener.java | 24 ++ .../async/kafka/config/RCKafkaConfig.java | 146 +++++++++++ .../config/RCKafkaEventListenerConfig.java | 60 +++++ ...CKafkaNotificationEventListenerConfig.java | 60 +++++ .../kafka/config/props/RCAsyncPropsKafka.java | 28 +++ .../kafka/config/props/RCKafkaProps.java | 6 + 41 files changed, 1934 insertions(+), 11 deletions(-) create mode 100644 async/async-kafka/async-kafka.gradle create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java create mode 100644 samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle create mode 100644 samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java create mode 100644 samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java create mode 100644 samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java create mode 100644 samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java create mode 100644 samples/async/async-kafka-sender-client/src/main/resources/application.yaml create mode 100644 starters/async-kafka-starter/async-kafka-starter.gradle create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaConfig.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaEventListenerConfig.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaNotificationEventListenerConfig.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java diff --git a/.gitignore b/.gitignore index 8a16dda7..47bd6ef3 100644 --- a/.gitignore +++ b/.gitignore @@ -644,4 +644,5 @@ MigrationBackup/ # End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows,gradle,java,intellij,visualstudio,eclipse contiperf-report -samples/async/local-example/ \ No newline at end of file +samples/async/local-example/ +.kafka-env \ No newline at end of file diff --git a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java index 0f73e4b1..5cda7121 100644 --- a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java +++ b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java @@ -5,7 +5,12 @@ import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.handlers.*; +import org.reactivecommons.async.api.handlers.CloudCommandHandler; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.QueryHandler; +import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; @@ -152,7 +157,7 @@ void handleDomainCommand() { @Test void handleCloudEventCommand() { - SomeCloudEventCommandHandler cloudCommandHandler = new SomeCloudEventCommandHandler(); + SomeCloudCommandHandler cloudCommandHandler = new SomeCloudCommandHandler(); registry.handleCloudEventCommand(name, cloudCommandHandler); @@ -197,7 +202,7 @@ void serveQueryWithLambda() { @Test void serveQueryWithTypeInference() { QueryHandler handler = new SomeQueryHandler(); - registry.serveQuery(name, handler,SomeDataClass.class); + registry.serveQuery(name, handler, SomeDataClass.class); assertThat(registry.getHandlers()).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); @@ -252,7 +257,7 @@ public Mono handle(Command message) { } } - private static class SomeCloudEventCommandHandler implements CloudCommandHandler { + private static class SomeCloudCommandHandler implements CloudCommandHandler { @Override public Mono handle(CloudEvent message) { return null; diff --git a/async/async-kafka/async-kafka.gradle b/async/async-kafka/async-kafka.gradle new file mode 100644 index 00000000..a24b92b5 --- /dev/null +++ b/async/async-kafka/async-kafka.gradle @@ -0,0 +1,12 @@ +ext { + artifactId = 'async-kafka' + artifactDescription = 'Async Kafka' +} + +dependencies { + api project(':async-commons-api') + api project(':domain-events-api') + api project(':async-commons') + api 'io.projectreactor.kafka:reactor-kafka:1.3.23' + api 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} \ No newline at end of file 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 new file mode 100644 index 00000000..d24f0a76 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import lombok.AllArgsConstructor; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivestreams.Publisher; + +@AllArgsConstructor +public class KafkaDomainEventBus implements DomainEventBus { + private final ReactiveMessageSender sender; + + @Override + public Publisher emit(DomainEvent event) { + return sender.send(event); + } + + @Override + public Publisher emit(CloudEvent event) { + return sender.send(event); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java new file mode 100644 index 00000000..7c5092de --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java @@ -0,0 +1,52 @@ +package org.reactivecommons.async.kafka; + +import lombok.Data; +import org.apache.kafka.common.header.Headers; +import org.reactivecommons.async.commons.communications.Message; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.HashMap; +import java.util.Map; + +import static org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter.CONTENT_TYPE; + + +@Data +public class KafkaMessage implements Message { + private final byte[] body; + private final Properties properties; + + @Data + public static class KafkaMessageProperties implements Properties { + private long contentLength; + private String key; + private String topic; + private Map headers = new HashMap<>(); + + @Override + public String getContentType() { + return (String) headers.get(CONTENT_TYPE); + } + } + + public static KafkaMessage fromDelivery(ReceiverRecord record) { + return new KafkaMessage(record.value(), createMessageProps(record)); + } + + private static Properties createMessageProps(ReceiverRecord record) { + Map headers = parseHeaders(record.headers()); + + final KafkaMessageProperties properties = new KafkaMessageProperties(); + properties.setHeaders(headers); + properties.setKey(record.key()); + properties.setTopic(record.topic()); + properties.setContentLength(record.value().length); + return properties; + } + + private static Map parseHeaders(Headers headers) { + Map parsedHeaders = new HashMap<>(); + headers.forEach(header -> parsedHeaders.put(header.key(), new String(header.value()))); + return parsedHeaders; + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java new file mode 100644 index 00000000..2a02808d --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java @@ -0,0 +1,30 @@ +package org.reactivecommons.async.kafka.communications; + +import lombok.AllArgsConstructor; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import reactor.core.publisher.Flux; +import reactor.kafka.receiver.KafkaReceiver; +import reactor.kafka.receiver.ReceiverOptions; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.List; + + +@AllArgsConstructor +public class ReactiveMessageListener { + private final ReceiverOptions receiverOptions; + + public Flux> listen(String groupId, List topics) { // Notification events + ReceiverOptions options = receiverOptions.consumerProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId); + return KafkaReceiver.create(options.subscription(topics)) + .receive(); + } + + public int getMaxConcurrency() { + Object property = receiverOptions.consumerProperty(ConsumerConfig.MAX_POLL_RECORDS_CONFIG); + if (property instanceof Integer) { + return (int) property; + } + return ConsumerConfig.DEFAULT_MAX_POLL_RECORDS; + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java new file mode 100644 index 00000000..7e13d05a --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java @@ -0,0 +1,91 @@ +package org.reactivecommons.async.kafka.communications; + +import lombok.SneakyThrows; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.Header; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.KafkaMessage; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.kafka.sender.KafkaSender; +import reactor.kafka.sender.SenderRecord; +import reactor.kafka.sender.SenderResult; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +public class ReactiveMessageSender { + private final ConcurrentHashMap> confirmations = new ConcurrentHashMap<>(); + private final CopyOnWriteArrayList>> fluxSinks = new CopyOnWriteArrayList<>(); + private final AtomicLong counter = new AtomicLong(); + + private final ExecutorService executorServiceConfirm = Executors.newFixedThreadPool(13, r -> new Thread(r, "KMessageSender1-" + counter.getAndIncrement())); + private final ExecutorService executorServiceEmit = Executors.newFixedThreadPool(13, r -> new Thread(r, "KMessageSender2-" + counter.getAndIncrement())); + + private final int senderCount = 4; + + private final MessageConverter messageConverter; + private final TopologyCreator topologyCreator; + + public ReactiveMessageSender(KafkaSender sender, MessageConverter messageConverter, + TopologyCreator topologyCreator) { + this.messageConverter = messageConverter; + this.topologyCreator = topologyCreator; + for (int i = 0; i < senderCount; ++i) { + Flux> source = Flux.create(fluxSinks::add); + sender.send(source) + .doOnNext(this::confirm) + .subscribe(); + } + } + + public Mono send(V message) { + return Mono.create(sink -> { + SenderRecord record = createRecord(message); + confirmations.put(record.key(), sink); + executorServiceEmit.submit(() -> fluxSinks.get((int) (System.currentTimeMillis() % senderCount)).next(record)); + }); + } + + private void confirm(SenderResult result) { + executorServiceConfirm.submit(() -> { + MonoSink sink = confirmations.remove(result.correlationMetadata()); + if (sink != null) { + if (result.exception() != null) { + sink.error(result.exception()); + } else { + sink.success(); + } + } + }); + } + + private SenderRecord createRecord(V object) { + KafkaMessage message = (KafkaMessage) messageConverter.toMessage(object); + ProducerRecord record = createProducerRecord(message); + return SenderRecord.create(record, message.getProperties().getKey()); // TODO: Review for Request-Reply + } + + @SneakyThrows + private ProducerRecord createProducerRecord(KafkaMessage message) { + topologyCreator.checkTopic(message.getProperties().getTopic()); + + List
headers = message.getProperties().getHeaders().entrySet().stream() + .map(entry -> new RecordHeader(entry.getKey(), entry.getValue() + .toString().getBytes(StandardCharsets.UTF_8))) + .collect(Collectors.toList()); + + return new ProducerRecord<>(message.getProperties().getTopic(), null, + message.getProperties().getKey(), message.getBody(), headers); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java new file mode 100644 index 00000000..3f8496e8 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.kafka.communications.exceptions; + +public class TopicNotFoundException extends RuntimeException { + public TopicNotFoundException(String message) { + super(message); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java new file mode 100644 index 00000000..242b7ab9 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java @@ -0,0 +1,26 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class KafkaCustomizations { + private Map topics = new HashMap<>(); + + public static KafkaCustomizations withTopic(String topic, TopicCustomization customization) { + KafkaCustomizations customizations = new KafkaCustomizations(); + customizations.getTopics().put(topic, customization); + return customizations; + } + + public KafkaCustomizations addTopic(String topic, TopicCustomization customization) { + this.getTopics().put(topic, customization); + return this; + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java new file mode 100644 index 00000000..64821e9f --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java @@ -0,0 +1,19 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TopicCustomization { + private String topic; + private int partitions; + private short replicationFactor; + private Map config; +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java new file mode 100644 index 00000000..9c1385cc --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java @@ -0,0 +1,76 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import lombok.SneakyThrows; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.ListTopicsOptions; +import org.apache.kafka.clients.admin.ListTopicsResult; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.common.errors.TopicExistsException; +import org.reactivecommons.async.kafka.communications.exceptions.TopicNotFoundException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class TopologyCreator { + public static final int TIMEOUT_MS = 60_000; + private final AdminClient adminClient; + private final KafkaCustomizations customizations; + private final Map existingTopics; + + public TopologyCreator(AdminClient adminClient, KafkaCustomizations customizations) { + this.adminClient = adminClient; + this.customizations = customizations; + this.existingTopics = getTopics(); + } + + @SneakyThrows + public Map getTopics() { + ListTopicsResult topics = adminClient.listTopics(new ListTopicsOptions().timeoutMs(TIMEOUT_MS)); + return topics.names().get().stream().collect(Collectors.toConcurrentMap(name -> name, name -> true)); + } + + public Mono createTopics(List topics) { + TopicCustomization.TopicCustomizationBuilder defaultBuilder = TopicCustomization.builder() + .partitions(-1) + .replicationFactor((short) -1); + + return Flux.fromIterable(topics) + .map(topic -> { + if (customizations.getTopics().containsKey(topic)) { + return customizations.getTopics().get(topic); + } + return defaultBuilder.topic(topic).build(); + }) + .map(this::toNewTopic) + .flatMap(this::createTopic) + .doOnNext(topic -> existingTopics.put(topic.name(), true)) + .then(); + } + + protected Mono createTopic(NewTopic topic) { + return Mono.fromFuture(adminClient.createTopics(List.of(topic)) + .all() + .toCompletionStage() + .toCompletableFuture()) + .thenReturn(topic) + .onErrorResume(TopicExistsException.class, e -> Mono.just(topic)); + } + + protected NewTopic toNewTopic(TopicCustomization customization) { + NewTopic topic = new NewTopic(customization.getTopic(), customization.getPartitions(), customization.getReplicationFactor()); + if (customization.getConfig() != null) { + return topic.configs(customization.getConfig()); + } + return topic; + } + + public void checkTopic(String topicName) { + if (!existingTopics.containsKey(topicName)) { + throw new TopicNotFoundException("Topic not found: " + topicName + ". Please create it before send a message."); + // TODO: should refresh topics?? getTopics(); + } + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java new file mode 100644 index 00000000..d1f79703 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java @@ -0,0 +1,61 @@ +package org.reactivecommons.async.kafka.converters.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.commons.exceptions.MessageConversionException; +import org.reactivecommons.async.kafka.KafkaMessage; +import org.reactivecommons.async.kafka.KafkaMessage.KafkaMessageProperties; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class KafkaJacksonMessageConverter extends JacksonMessageConverter { + + public KafkaJacksonMessageConverter(ObjectMapper objectMapper) { + super(objectMapper); + } + + @Override + public Message toMessage(Object object) { + byte[] bytes; + try { + String jsonString = this.objectMapper.writeValueAsString(object); + bytes = jsonString.getBytes(StandardCharsets.UTF_8); + } catch (IOException e) { + throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); + } + KafkaMessageProperties props = buildProperties(object); + return new KafkaMessage(bytes, props); + } + + private KafkaMessageProperties buildProperties(Object message) { + KafkaMessageProperties props = new KafkaMessageProperties(); + Map headers = new HashMap<>(); + props.setHeaders(headers); + + if (message instanceof CloudEvent) { + CloudEvent cloudEvent = (CloudEvent) message; + props.setKey(cloudEvent.getId()); + props.setTopic(cloudEvent.getType()); + + headers.put(CONTENT_TYPE, APPLICATION_CLOUD_EVENT_JSON); + return props; + } + + if (message instanceof DomainEvent) { + DomainEvent domainEvent = (DomainEvent) message; + props.setKey(domainEvent.getEventId()); + props.setTopic(domainEvent.getName()); + + headers.put(CONTENT_TYPE, APPLICATION_JSON); + return props; + } + // TODO: Add Command and AsyncQuery support + throw new IllegalArgumentException("Message type not supported: " + message.getClass().getName()); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java new file mode 100644 index 00000000..50a19375 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java @@ -0,0 +1,75 @@ +package org.reactivecommons.async.kafka.listeners; + +import lombok.extern.java.Log; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +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.kafka.communications.ReactiveMessageListener; +import reactor.core.publisher.Mono; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.function.Function; + +@Log +public class ApplicationEventListener extends GenericMessageListener { + + private final MessageConverter messageConverter; + private final HandlerResolver resolver; + + + public ApplicationEventListener(ReactiveMessageListener receiver, + HandlerResolver resolver, + MessageConverter messageConverter, + boolean withDLQRetry, + boolean createTopology, + long maxRetries, + int retryDelay, + DiscardNotifier discardNotifier, + CustomReporter errorReporter, + String appName) { + super(receiver, withDLQRetry, createTopology, maxRetries, retryDelay, discardNotifier, + "event", errorReporter, appName + "-events", resolver.getEventNames()); + this.resolver = resolver; + this.messageConverter = messageConverter; + } + + @Override + protected Function> rawMessageHandler(String executorPath) { + final RegisteredEventListener handler = resolver.getEventListener(executorPath); + + Function converter = resolveConverter(handler); + final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); + + return msj -> executor + .execute(msj) + .cast(Object.class); + } + + protected String getExecutorPath(ReceiverRecord msj) { + return msj.topic(); + } + + @Override + protected Object parseMessageForReporter(Message msj) { + return messageConverter.readDomainEventStructure(msj); + } + + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } +} + + diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java new file mode 100644 index 00000000..bf4305e5 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java @@ -0,0 +1,78 @@ +package org.reactivecommons.async.kafka.listeners; + +import lombok.extern.java.Log; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +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.kafka.communications.ReactiveMessageListener; +import reactor.core.publisher.Mono; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +@Log +public class ApplicationNotificationsListener extends GenericMessageListener { + + private final MessageConverter messageConverter; + private final HandlerResolver resolver; + + + public ApplicationNotificationsListener(ReactiveMessageListener receiver, + HandlerResolver resolver, + MessageConverter messageConverter, + boolean withDLQRetry, + boolean createTopology, + long maxRetries, + int retryDelay, + DiscardNotifier discardNotifier, + CustomReporter errorReporter, + String appName) { + super(receiver, withDLQRetry, createTopology, maxRetries, retryDelay, discardNotifier, + "event", errorReporter, appName + "-notification-" + UUID.randomUUID(), + resolver.getNotificationNames()); + this.resolver = resolver; + this.messageConverter = messageConverter; + } + + @Override + protected Function> rawMessageHandler(String executorPath) { + final RegisteredEventListener handler = resolver.getEventListener(executorPath); + + Function converter = resolveConverter(handler); + final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); + + return msj -> executor + .execute(msj) + .cast(Object.class); + } + + protected String getExecutorPath(ReceiverRecord msj) { + return msj.topic(); + } + + @Override + protected Object parseMessageForReporter(Message msj) { + return messageConverter.readDomainEventStructure(msj); + } + + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } +} + + diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java new file mode 100644 index 00000000..da47e237 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java @@ -0,0 +1,233 @@ +package org.reactivecommons.async.kafka.listeners; + +import lombok.extern.java.Log; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.FallbackStrategy; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.utils.LoggerSubscriber; +import org.reactivecommons.async.kafka.KafkaMessage; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; +import reactor.kafka.receiver.ReceiverRecord; +import reactor.util.retry.Retry; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.logging.Level; + +import static java.lang.String.format; +import static java.util.function.Function.identity; + +@Log +public abstract class GenericMessageListener { + public static final int DEFAULT_RETRIES = 10; + private final ConcurrentHashMap>> handlers = new ConcurrentHashMap<>(); + private final ReactiveMessageListener messageListener; + private final Scheduler scheduler = Schedulers.newParallel(getClass().getSimpleName(), 12); + private final Scheduler errorReporterScheduler = Schedulers.newBoundedElastic(4, 256, + "errorReporterScheduler"); + + private final List topics; + private final String groupId; + private final boolean useDLQ; + private final boolean createTopology; + private final long maxRetries; + private final Duration retryDelay; + private final DiscardNotifier discardNotifier; + private final String objectType; + private final CustomReporter customReporter; + private volatile Flux> messageFlux; + + public GenericMessageListener(ReactiveMessageListener listener, boolean useDLQ, boolean createTopology, + long maxRetries, long retryDelay, DiscardNotifier discardNotifier, + String objectType, CustomReporter customReporter, String groupId, List topics) { + this.groupId = groupId; + this.topics = topics; + this.messageListener = listener; + this.createTopology = createTopology; + this.maxRetries = maxRetries; + this.retryDelay = Duration.ofMillis(retryDelay); + this.useDLQ = useDLQ; + this.discardNotifier = discardNotifier; + this.objectType = objectType; + this.customReporter = customReporter; + } + + private boolean hasRetries() { + return maxRetries != -1; + } + + protected Mono setUpBindings(TopologyCreator creator) { + return creator.createTopics(topics); + } + + public void startListener(TopologyCreator creator) { + log.log(Level.INFO, "Using max concurrency {0}, in receiver for topics: {1}", + + new Object[]{messageListener.getMaxConcurrency(), topics}); + if (useDLQ) { + log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", + new Object[]{maxRetries}); + } else { + log.log(Level.INFO, "ATTENTION! Using infinite fast retries as Retry Strategy"); + } + + if (createTopology) { + this.messageFlux = setUpBindings(creator) + .thenMany(this.messageListener.listen(groupId, topics) + .doOnError(err -> log.log(Level.SEVERE, "Error listening queue", err)) + .transform(this::consumeFaultTolerant)); + } else { + this.messageFlux = this.messageListener.listen(groupId, topics) + .doOnError(err -> log.log(Level.SEVERE, "Error listening queue", err)) + .transform(this::consumeFaultTolerant); + } + + onTerminate(); + } + + private void onTerminate() { + messageFlux.doOnTerminate(this::onTerminate) + .subscribe(new LoggerSubscriber<>(getClass().getName())); + } + + private Flux> consumeFaultTolerant(Flux> messageFlux) { + return messageFlux.flatMap(msj -> { + final Instant init = Instant.now(); + return handle(msj, init) + .doOnSuccess(record -> record.receiverOffset().acknowledge()) + .onErrorResume(err -> requeueOrAck(msj, err, init)); + }, messageListener.getMaxConcurrency()); + } + + protected Mono> handle(ReceiverRecord msj, Instant initTime) { + try { + final String executorPath = getExecutorPath(msj); + final Function> handler = getExecutor(executorPath); + final Message message = KafkaMessage.fromDelivery(msj); + + Mono flow = Mono.defer(() -> handler.apply(message)) + .transform(enrichPostProcess(message)); + if (hasRetries()) { + flow = flow.retryWhen(Retry.fixedDelay(maxRetries, retryDelay)) + .onErrorMap(err -> { + if (err.getMessage() != null && err.getMessage().contains("Retries exhausted")) { + log.warning(err.getMessage()); + return err.getCause(); + } + return err; + }); + } + return flow.doOnSuccess(o -> logExecution(executorPath, initTime, true)) + .subscribeOn(scheduler) + .thenReturn(msj); + } catch (Exception e) { + log.log(Level.SEVERE, format("ATTENTION !! Outer error protection reached for %s, in Async Consumer!! " + + "Severe Warning! ", msj.key())); + return Mono.error(e); + } + } + + private Mono> requeueOrAck(ReceiverRecord msj, Throwable err, + Instant init) { + final Message message = KafkaMessage.fromDelivery(msj); + reportErrorMetric(msj, init); + sendErrorToCustomReporter(err, message, hasRetries()); + if (hasRetries()) { // Discard + logError(err, msj, FallbackStrategy.DEFINITIVE_DISCARD); + if (useDLQ) { + return discardNotifier + .notifyDiscard(message) + .doOnSuccess(_a -> msj.receiverOffset().acknowledge()) + .thenReturn(msj); + } + return Mono.just(msj); + } else { // infinity fast retries + logError(err, msj, FallbackStrategy.FAST_RETRY); + return Mono.just(msj).delayElement(retryDelay); + } + } + + private void logExecution(String executorPath, Instant initTime, boolean success) { + try { + final Instant afterExecutionTime = Instant.now(); + final long timeElapsed = Duration.between(initTime, afterExecutionTime).toMillis(); + doLogExecution(executorPath, timeElapsed); + customReporter.reportMetric(objectType, executorPath, timeElapsed, success); + } catch (Exception e) { + log.log(Level.WARNING, "Unable to send execution metrics!", e); + } + + } + + private void reportErrorMetric(ReceiverRecord msj, Instant initTime) { + String executorPath; + try { + executorPath = getExecutorPath(msj); + } catch (Exception e) { + executorPath = "unknown"; + } + logExecution(executorPath, initTime, false); + } + + private void doLogExecution(String executorPath, long timeElapsed) { + log.log(Level.FINE, String.format("%s with path %s handled, took %d ms", + objectType, executorPath, timeElapsed)); + } + + + protected void logError(Throwable err, ReceiverRecord msj, FallbackStrategy strategy) { + String messageID = msj.key(); + try { + log.log(Level.SEVERE, format("Error encounter while processing message %s: %s", messageID, err.toString()), err); + log.warning(format("Message %s Headers: %s", messageID, msj.headers().toString())); + log.warning(format("Message %s Body: %s", messageID, new String(msj.value()))); + } catch (Exception e) { + log.log(Level.SEVERE, "Error Login message Content!!", e); + } finally { + log.warning(format(strategy.message, messageID)); + } + } + + private Function> getExecutor(String path) { + final Function> handler = handlers.get(path); + return handler != null ? handler : computeRawMessageHandler(path); + } + + private Function> computeRawMessageHandler(String commandId) { + return handlers.computeIfAbsent(commandId, s -> + rawMessageHandler(commandId) + ); + } + + protected abstract Function> rawMessageHandler(String executorPath); + + protected abstract String getExecutorPath(ReceiverRecord msj); + + protected Function, Mono> enrichPostProcess(Message msg) { + return identity(); + } + + private void sendErrorToCustomReporter(final Throwable err, final Message message, final boolean redelivered) { + try { + customReporter.reportError(err, message, parseMessageForReporter(message), redelivered) + .subscribeOn(errorReporterScheduler) + .doOnError(t -> log.log(Level.WARNING, "Error sending error to external reporter", t)) + .subscribe(); + } catch (Throwable t) { + log.log(Level.WARNING, "Error in scheduler when sending error to external reporter", t); + } + } + + protected abstract Object parseMessageForReporter(Message msj); +} + + diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java new file mode 100644 index 00000000..4ac1dec5 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java @@ -0,0 +1,57 @@ +package org.reactivecommons.async.kafka; + +import org.apache.kafka.common.header.internals.RecordHeaders; +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.communications.Message; +import reactor.kafka.receiver.ReceiverRecord; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KafkaMessageTest { + + @Mock + private ReceiverRecord record; + + @Test + void shouldParse() { + // Arrange + RecordHeaders headers = new RecordHeaders(); + headers.add("content-type", "application/json".getBytes()); + when(record.value()).thenReturn("value".getBytes()); + when(record.key()).thenReturn("key"); + when(record.topic()).thenReturn("topic"); + when(record.headers()).thenReturn(headers); + // Act + Message message = KafkaMessage.fromDelivery(record); + // Assert + assertEquals("key", message.getProperties().getKey()); + assertEquals("topic", message.getProperties().getTopic()); + assertEquals("application/json", message.getProperties().getContentType()); + assertEquals(5, message.getProperties().getContentLength()); + assertEquals("value", new String(message.getBody())); + } + + @Test + void shouldParseWhenNoContentType() { + // Arrange + RecordHeaders headers = new RecordHeaders(); + when(record.value()).thenReturn("value".getBytes()); + when(record.key()).thenReturn("key"); + when(record.topic()).thenReturn("topic"); + when(record.headers()).thenReturn(headers); + // Act + Message message = KafkaMessage.fromDelivery(record); + // Assert + assertEquals("key", message.getProperties().getKey()); + assertEquals("topic", message.getProperties().getTopic()); + assertNull(message.getProperties().getContentType()); + assertEquals(5, message.getProperties().getContentLength()); + assertEquals("value", new String(message.getBody())); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java new file mode 100644 index 00000000..6b5b035e --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java @@ -0,0 +1,54 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopicCustomization; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class KafkaCustomizationsTest { + + private KafkaCustomizations customizations; + + @BeforeEach + void setUp() { + customizations = new KafkaCustomizations(); + } + + @Test + void testWithTopic() { + String topic = "testTopic"; + Map config = new HashMap<>(); + config.put("cleanup.policy", "compact"); + TopicCustomization customization = new TopicCustomization(topic, 3, (short) 1, config); + KafkaCustomizations result = KafkaCustomizations.withTopic(topic, customization); + + assertNotNull(result); + assertEquals(1, result.getTopics().size()); + assertEquals(customization, result.getTopics().get(topic)); + } + + @Test + void testAddTopic() { + String topic1 = "testTopic1"; + Map config1 = new HashMap<>(); + config1.put("cleanup.policy", "compact"); + TopicCustomization customization1 = new TopicCustomization(topic1, 3, (short) 1, config1); + customizations.addTopic(topic1, customization1); + + String topic2 = "testTopic2"; + Map config2 = new HashMap<>(); + config2.put("retention.ms", "60000"); + TopicCustomization customization2 = new TopicCustomization(topic2, 5, (short) 2, config2); + customizations.addTopic(topic2, customization2); + + assertEquals(2, customizations.getTopics().size()); + assertEquals(customization1, customizations.getTopics().get(topic1)); + assertEquals(customization2, customizations.getTopics().get(topic2)); + } +} \ No newline at end of file diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java new file mode 100644 index 00000000..87c15d24 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java @@ -0,0 +1,96 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.CreateTopicsResult; +import org.apache.kafka.clients.admin.ListTopicsOptions; +import org.apache.kafka.clients.admin.ListTopicsResult; +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.reactivecommons.async.kafka.communications.exceptions.TopicNotFoundException; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TopologyCreatorTest { + + private TopologyCreator creator; + @Mock + private AdminClient adminClient; + @Mock + private ListTopicsResult listTopicsResult; + @Mock + private CreateTopicsResult createTopicsResult; + private KafkaCustomizations customizations; + + @BeforeEach + void setUp() { + Map config = new HashMap<>(); + config.put("cleanup.policy", "compact"); + TopicCustomization customization = new TopicCustomization("topic1", 3, (short) 1, config); + customizations = KafkaCustomizations.withTopic("topic1", customization); + } + + @Test + void shouldCreateTopics() { + // Arrange + KafkaFutureImpl> names = new KafkaFutureImpl<>(); + names.complete(Set.of("topic1", "topic2")); + doReturn(names).when(listTopicsResult).names(); + when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); + + KafkaFutureImpl create = new KafkaFutureImpl<>(); + create.complete(null); + doReturn(create).when(createTopicsResult).all(); + when(adminClient.createTopics(any())).thenReturn(createTopicsResult); + creator = new TopologyCreator(adminClient, customizations); + // Act + Mono flow = creator.createTopics(List.of("topic1", "topic2")); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldCheckTopics() { + // Arrange + KafkaFutureImpl> names = new KafkaFutureImpl<>(); + names.complete(Set.of("topic1", "topic2")); + doReturn(names).when(listTopicsResult).names(); + when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); + creator = new TopologyCreator(adminClient, customizations); + // Act + creator.checkTopic("topic1"); + // Assert + verify(listTopicsResult, times(1)).names(); + } + + @Test + void shouldFailWhenCheckTopics() { + // Arrange + KafkaFutureImpl> names = new KafkaFutureImpl<>(); + names.complete(Set.of("topic1", "topic2")); + doReturn(names).when(listTopicsResult).names(); + when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); + creator = new TopologyCreator(adminClient, customizations); + // Assert + assertThrows(TopicNotFoundException.class, () -> + // Act + creator.checkTopic("topic3")); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java new file mode 100644 index 00000000..cdec5482 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java @@ -0,0 +1,88 @@ +package org.reactivecommons.async.kafka.converters.json; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.jackson.JsonCloudEventData; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class KafkaJacksonMessageConverterTest { + private static KafkaJacksonMessageConverter converter; + private static ObjectMapper objectMapper; + + @BeforeAll + static void setUp() { + ObjectMapperSupplier supplier = new DefaultObjectMapperSupplier(); + objectMapper = supplier.get(); + converter = new KafkaJacksonMessageConverter(objectMapper); + } + + @Test + void shouldSerializeDomainEvent() { + // Arrange + String id = UUID.randomUUID().toString(); + MyEvent event = new MyEvent("name", 1); + DomainEvent testEvent = new DomainEvent<>("test", id, event); + String expectedJson = "{\"name\":\"test\",\"eventId\":\"" + id + "\",\"data\":{\"name\":\"name\",\"age\":1}}"; + // Act + Message message = converter.toMessage(testEvent); + // Assert + assertEquals("test", message.getProperties().getTopic()); + assertEquals(id, message.getProperties().getKey()); + assertEquals("application/json", message.getProperties().getContentType()); + assertEquals(expectedJson, new String(message.getBody())); + } + + @Test + void shouldSerializeCloudEvent() throws JsonProcessingException { + // Arrange + String id = UUID.randomUUID().toString(); + MyEvent event = new MyEvent("name", 1); + OffsetDateTime dateTime = OffsetDateTime.now(); + CloudEvent testCloudEvent = CloudEventBuilder.v1() + .withId(id) + .withSource(URI.create("https://reactivecommons.org/events")) + .withType("test") + .withDataContentType("application/json") + .withTime(dateTime) + .withData(objectMapper.writeValueAsBytes(event)) + .build(); + + String expectedJson = "{\"specversion\":\"1.0\",\"id\":\"" + id + + "\",\"source\":\"https://reactivecommons.org/events\",\"type\":\"test\"," + + "\"datacontenttype\":\"application/json\",\"time\":\"" + dateTime + + "\",\"data\":{\"name\":\"name\",\"age\":1}}"; + // Act + Message message = converter.toMessage(testCloudEvent); + // Assert + assertEquals("test", message.getProperties().getTopic()); + assertEquals(id, message.getProperties().getKey()); + assertEquals("application/cloudevents+json", message.getProperties().getContentType()); + assertEquals(expectedJson, new String(message.getBody())); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class MyEvent { + private String name; + private int age; + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java new file mode 100644 index 00000000..e05ef076 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java @@ -0,0 +1,99 @@ +package org.reactivecommons.async.kafka.listeners; + +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.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings({"rawtypes", "unchecked"}) +@ExtendWith(MockitoExtension.class) +class ApplicationEventListenerTest { + + @Mock + private ReactiveMessageListener receiver; + @Mock + private HandlerResolver resolver; + @Mock + private MessageConverter messageConverter; + @Mock + private Message message; + + private ApplicationEventListener applicationEventListener; + + @BeforeEach + void setup() { + applicationEventListener = new ApplicationEventListener( + receiver, + resolver, + messageConverter, + true, + true, + 3, + 1000, + null, + null, + "testApp" + ); + + } + + @Test + void shouldHandleRawMessageSuccessfully() { + DomainEvent event = new DomainEvent<>("sample", "id", "data"); + DomainEventHandler domainEventHandler = mock(DomainEventHandler.class); + when(domainEventHandler.handle(event)).thenReturn(Mono.just("Handled")); + RegisteredEventListener registeredEventListenerMock = mock(RegisteredEventListener.class); + when(registeredEventListenerMock.getHandler()).thenReturn(domainEventHandler); + when(registeredEventListenerMock.getInputClass()).thenReturn(Object.class); + when(resolver.getEventListener(anyString())).thenReturn(registeredEventListenerMock); + when(messageConverter.readDomainEvent(any(Message.class), any(Class.class))).thenReturn(event); + + Mono flow = applicationEventListener.rawMessageHandler("executorPath").apply(message); + + StepVerifier.create(flow) + .expectNext("Handled") + .verifyComplete(); + + verify(resolver, times(1)).getEventListener(anyString()); + verify(messageConverter, times(1)).readDomainEvent(any(Message.class), any(Class.class)); + } + + @Test + void shouldHandleRawMessageSuccessfullyWhenCloudEvent() { + CloudEvent event = mock(CloudEvent.class); + EventHandler domainEventHandler = mock(CloudEventHandler.class); + when(domainEventHandler.handle(event)).thenReturn(Mono.empty()); + RegisteredEventListener registeredEventListenerMock = mock(RegisteredEventListener.class); + when(registeredEventListenerMock.getHandler()).thenReturn(domainEventHandler); + when(resolver.getEventListener(anyString())).thenReturn(registeredEventListenerMock); + when(messageConverter.readCloudEvent(any(Message.class))).thenReturn(event); + + Mono flow = applicationEventListener.rawMessageHandler("executorPath").apply(message); + + StepVerifier.create(flow) + .verifyComplete(); + + verify(resolver, times(1)).getEventListener(anyString()); + verify(messageConverter, times(1)).readCloudEvent(any(Message.class)); + } +} \ No newline at end of file diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java new file mode 100644 index 00000000..fb7b23b7 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java @@ -0,0 +1,125 @@ +package org.reactivecommons.async.kafka.listeners; + +import org.apache.kafka.common.header.Headers; +import org.apache.kafka.common.header.internals.RecordHeaders; +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.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.kafka.receiver.ReceiverOffset; +import reactor.kafka.receiver.ReceiverRecord; +import reactor.test.StepVerifier; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings({"rawtypes", "unchecked"}) +@ExtendWith(MockitoExtension.class) +class GenericMessageListenerTest { + + @Mock + private ReactiveMessageListener receiver; + @Mock + private Message message; + @Mock + private TopologyCreator topologyCreator; + + @Mock + private RegisteredEventListener handler; + + SampleListener setup(Function> handler) { + return new SampleListener( + receiver, + true, + true, + 1, + 1, + mock(DiscardNotifier.class), + "event", + mock(CustomReporter.class), + "appName", + List.of("topic"), + handler + ); + + } + + @Test + void shouldStartListener() { + // Arrange + ReceiverRecord record = mock(ReceiverRecord.class); + when(record.topic()).thenReturn("topic"); + when(record.value()).thenReturn("message".getBytes(StandardCharsets.UTF_8)); + Headers header = new RecordHeaders().add("contentType", "application/json".getBytes(StandardCharsets.UTF_8)); + when(record.headers()).thenReturn(header); + when(record.key()).thenReturn("key"); + ReceiverOffset receiverOffset = mock(ReceiverOffset.class); + when(record.receiverOffset()).thenReturn(receiverOffset); + + Flux> flux = Flux.just(record); + when(receiver.listen(anyString(), any(List.class))).thenReturn(flux); + when(receiver.getMaxConcurrency()).thenReturn(1); + when(topologyCreator.createTopics(any(List.class))).thenReturn(Mono.empty()); + + final AtomicReference> sink = new AtomicReference<>(); + Mono flow = Mono.create(sink::set); + SampleListener sampleListener = setup(message1 -> { + sink.get().success(""); + return Mono.empty(); + }); + // Act + sampleListener.startListener(topologyCreator); + StepVerifier.create(flow).expectNext("").verifyComplete(); + // Assert + verify(topologyCreator, times(1)).createTopics(any(List.class)); + verify(receiverOffset, atLeastOnce()).acknowledge(); + } + + + public static class SampleListener extends GenericMessageListener { + private final Function> handler; + + public SampleListener(ReactiveMessageListener listener, boolean useDLQ, boolean createTopology, long maxRetries, + long retryDelay, DiscardNotifier discardNotifier, String objectType, + CustomReporter customReporter, String groupId, List topics, + Function> handler) { + super(listener, useDLQ, createTopology, maxRetries, retryDelay, discardNotifier, objectType, customReporter, + groupId, topics); + this.handler = handler; + } + + @Override + protected Function> rawMessageHandler(String executorPath) { + return handler; + } + + @Override + protected String getExecutorPath(ReceiverRecord msj) { + return msj.topic(); + } + + @Override + protected Object parseMessageForReporter(Message msj) { + return null; + } + } +} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java index 8efba6ce..1226625f 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java @@ -23,6 +23,7 @@ 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.DLQDiscardNotifier; import org.reactivecommons.async.rabbit.DynamicRegistryImp; import org.reactivecommons.async.rabbit.RabbitDomainEventBus; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java index be1346bb..d55addd5 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java @@ -19,7 +19,6 @@ import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java index fb272ab4..0376ae96 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java @@ -1,6 +1,5 @@ package org.reactivecommons.async.rabbit.converters.json; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; @@ -13,7 +12,6 @@ import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; -import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; import java.io.IOException; import java.net.URI; @@ -65,7 +63,7 @@ void readValue() { } @Test - void readCloudEvent() throws JsonProcessingException { + void readCloudEvent() { Date date = new Date(); CloudEvent command = CloudEventBuilder.v1() // .withId(UUID.randomUUID().toString()) // diff --git a/gradle.properties b/gradle.properties index 7a1b628c..279b9609 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ version=5.0.0-alpha -toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter +toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter onlyUpdater=true \ No newline at end of file diff --git a/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle b/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle new file mode 100644 index 00000000..78766eea --- /dev/null +++ b/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle @@ -0,0 +1,9 @@ +apply plugin: 'org.springframework.boot' + +dependencies { + implementation project(':shared') + implementation project(':async-kafka-starter') + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'io.micrometer:micrometer-registry-prometheus' +} \ 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 new file mode 100644 index 00000000..f2c87a97 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java @@ -0,0 +1,17 @@ +package sample; + +import lombok.extern.java.Log; +import org.reactivecommons.async.kafka.annotations.EnableDomainEventBus; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@Log +//@EnableDirectAsyncGateway +@EnableDomainEventBus +@SpringBootApplication +public class EDASampleSenderApp { + + public static void main(String[] args) { + SpringApplication.run(EDASampleSenderApp.class, args); + } +} 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 new file mode 100644 index 00000000..525236ea --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java @@ -0,0 +1,26 @@ +package sample; + +import org.reactivecommons.async.kafka.config.RCKafkaConfig; +import org.reactivecommons.async.kafka.config.props.RCAsyncPropsKafka; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.io.IOException; +import java.nio.file.Path; + +@Configuration +public class KafkaConfig { + + @Bean + @Primary + public RCAsyncPropsKafka kafkaProps() throws IOException { + RCAsyncPropsKafka kafkaProps = new RCAsyncPropsKafka(); + kafkaProps.setCreateTopology(true); + kafkaProps.setMaxRetries(5); + kafkaProps.setRetryDelay(1000); + kafkaProps.setWithDLQRetry(true); + kafkaProps.setKafkaProps(RCKafkaConfig.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 new file mode 100644 index 00000000..ababe387 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java @@ -0,0 +1,36 @@ +package sample; + +import io.cloudevents.CloudEvent; +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.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import reactor.core.publisher.Mono; +import sample.model.Constants; + +@Log4j2 +@Configuration +@EnableEventListeners +public class ListenerConfig { + + @Bean + @Primary + public HandlerRegistry handlerRegistrySubs() { + return HandlerRegistry.register() + .listenEvent(Constants.DATA_RESET, this::reset, String.class) + .listenCloudEvent("event-name", this::reset2); + } + + private Mono reset2(CloudEvent cloudEvent) { + log.info("reset2: " + cloudEvent); + return Mono.empty(); + } + + public Mono reset(DomainEvent ignored) { + log.info("reset: {}", ignored); + return Mono.empty(); + } +} diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java new file mode 100644 index 00000000..98cf4009 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java @@ -0,0 +1,127 @@ +package sample; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.jackson.JsonCloudEventData; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; +import sample.model.Constants; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.UUID; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + + +@RestController +@RequiredArgsConstructor +public class SampleRestController { + // private final DirectAsyncGateway directAsyncGateway; + private final DomainEventBus domainEventBus; + private final ObjectMapperSupplier supplier; + private final String target = "receiver-eda"; + + // Notification Event + @DeleteMapping(path = "/api/teams/fail", produces = APPLICATION_JSON_VALUE) + public Mono> resetData() { + DomainEvent event = new DomainEvent<>(Constants.DATA_RESET, UUID.randomUUID().toString(), ""); + return Mono.from(domainEventBus.emit(event)).thenReturn(event); + } + + // Notification Event + @DeleteMapping(path = "/api/teams/ok", produces = APPLICATION_JSON_VALUE) + public Mono> event() { + DomainEvent event = new DomainEvent<>("event-name", UUID.randomUUID().toString(), ""); + return Mono.from(domainEventBus.emit(event)).thenReturn(event); + } + + // Notification Event + @DeleteMapping(path = "/api/teams/cloud/event", produces = APPLICATION_JSON_VALUE) + public Mono cloudEvent() throws JsonProcessingException { + SampleEvent eventData = new SampleEvent(); + eventData.setName("Juan"); + eventData.setDescription("A software developer"); + + CloudEvent event = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactive-commons.org/foos")) + .withType("event-name") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(supplier.get().valueToTree(eventData))) + .build(); + return Mono.from(domainEventBus.emit(event)).thenReturn(event); + } +// +// // Query +// @GetMapping(path = "/api/teams", produces = APPLICATION_JSON_VALUE) +// public Mono getTeams() { +// CloudEvent query = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.GET_TEAMS) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes("")) +// .build(); +// return directAsyncGateway.requestReply(query, target, CloudEvent.class) +// .map(event -> CloudEventBuilderExt.fromCloudEventData(event, Teams.class)); +// } +// +// // Query +// @GetMapping(path = "/api/teams/{team}", produces = APPLICATION_JSON_VALUE) +// public Mono getTeamMembers(@PathVariable("team") String team) { +// CloudEvent query = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.GET_TEAM_MEMBERS) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes(team)) +// .build(); +// return directAsyncGateway.requestReply(query, target, CloudEvent.class) +// .map(event -> CloudEventBuilderExt.fromCloudEventData(event, Members.class)); +// } +// +// // Command +// @PostMapping(path = "/api/teams/{team}/members", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) +// public Mono addMember(@PathVariable("team") String team, @RequestBody Member member) { +// AddMemberCommand commandData = AddMemberCommand.builder().member(member).teamName(team).build(); +// CloudEvent command = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.ADD_MEMBER) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes(commandData)) +// .build(); +// return directAsyncGateway.sendCommand(command, target).thenReturn(commandData); +// } +// +// // Event +// @DeleteMapping(path = "/api/teams/{team}/members/{member}", produces = APPLICATION_JSON_VALUE) +// public Mono removeMember(@PathVariable("team") String team, +// @PathVariable("member") String member) { +// RemovedMemberEvent eventData = RemovedMemberEvent.builder().teamName(team).username(member).build(); +// CloudEvent event = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.MEMBER_REMOVED) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes(eventData)) +// .build(); +// return Mono.from(domainEventBus.emit(event)).thenReturn(eventData); +// } + + @Data + public static class SampleEvent { + private String name; + private String description; + } +} diff --git a/samples/async/async-kafka-sender-client/src/main/resources/application.yaml b/samples/async/async-kafka-sender-client/src/main/resources/application.yaml new file mode 100644 index 00000000..323bc046 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/resources/application.yaml @@ -0,0 +1,17 @@ +spring: + application: + name: sender-eda + rabbitmq: + virtual-host: / +server: + port: 4001 +management: + endpoint: + health: + show-details: always + endpoints: + web: + exposure: + include: health,prometheus + + diff --git a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java index 3e5f951f..e42ff4eb 100644 --- a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java +++ b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java @@ -3,7 +3,6 @@ import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; import lombok.RequiredArgsConstructor; -import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; diff --git a/starters/async-kafka-starter/async-kafka-starter.gradle b/starters/async-kafka-starter/async-kafka-starter.gradle new file mode 100644 index 00000000..e5edebab --- /dev/null +++ b/starters/async-kafka-starter/async-kafka-starter.gradle @@ -0,0 +1,16 @@ +ext { + artifactId = 'async-commons-kafka-starter' + artifactDescription = 'Async Commons Starter' +} + +dependencies { + api project(':async-kafka') + compileOnly 'org.springframework.boot:spring-boot-starter' + compileOnly 'org.springframework.boot:spring-boot-starter-actuator' + 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-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java new file mode 100644 index 00000000..f95879f0 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableDomainEventBus.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.kafka.annotations; + +import org.reactivecommons.async.kafka.config.RCKafkaConfig; +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; + + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import(RCKafkaConfig.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-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java new file mode 100644 index 00000000..b70cee39 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.kafka.annotations; + +import org.reactivecommons.async.kafka.config.RCKafkaConfig; +import org.reactivecommons.async.kafka.config.RCKafkaEventListenerConfig; +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; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import({RCKafkaEventListenerConfig.class, RCKafkaConfig.class}) +@Configuration +public @interface EnableEventListeners { +} + + + diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java new file mode 100644 index 00000000..a96a0f26 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java @@ -0,0 +1,24 @@ +package org.reactivecommons.async.kafka.annotations; + +import org.reactivecommons.async.kafka.config.RCKafkaConfig; +import org.reactivecommons.async.kafka.config.RCKafkaNotificationEventListenerConfig; +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; + + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import({RCKafkaNotificationEventListenerConfig.class, RCKafkaConfig.class}) +@Configuration +public @interface EnableNotificationListener { +} + + + 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 new file mode 100644 index 00000000..4ecfd4ad --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaConfig.java @@ -0,0 +1,146 @@ +package org.reactivecommons.async.kafka.config; + +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.producer.ProducerConfig; +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.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +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.RCAsyncPropsKafka; +import org.reactivecommons.async.kafka.config.props.RCKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.springframework.beans.factory.annotation.Value; +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 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 static org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG; +import static org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG; +import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; +import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; + +@Configuration +@EnableConfigurationProperties({RCAsyncPropsKafka.class}) +public class RCKafkaConfig { + // Sender + @Bean + @ConditionalOnMissingBean(DomainEventBus.class) + public DomainEventBus kafkaDomainEventBus(ReactiveMessageSender sender) { + return new KafkaDomainEventBus(sender); + } + + @Bean + @ConditionalOnMissingBean(ReactiveMessageSender.class) + public ReactiveMessageSender kafkaReactiveMessageSender(KafkaSender kafkaSender, + MessageConverter converter, TopologyCreator topologyCreator) { + return new ReactiveMessageSender(kafkaSender, converter, topologyCreator); + } + + @Bean + @ConditionalOnMissingBean(KafkaSender.class) + public KafkaSender kafkaSender(RCAsyncPropsKafka config, @Value("${spring.application.name}") String clientId) { + RCKafkaProps props = config.getKafkaProps(); + props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId); + props.put(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); + SenderOptions senderOptions = SenderOptions.create(props); + return KafkaSender.create(senderOptions); + } + + // Receiver + + @Bean + @ConditionalOnMissingBean(ReactiveMessageListener.class) + public ReactiveMessageListener kafkaReactiveMessageListener(ReceiverOptions receiverOptions) { + return new ReactiveMessageListener(receiverOptions); + } + + @Bean + @ConditionalOnMissingBean(ReceiverOptions.class) + public ReceiverOptions kafkaReceiverOptions(RCAsyncPropsKafka config) { + RCKafkaProps props = config.getKafkaProps(); + props.put(KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class); + return ReceiverOptions.create(props); + } + + // Shared + + @Bean + @ConditionalOnMissingBean(TopologyCreator.class) + public TopologyCreator kafkaTopologyCreator(RCAsyncPropsKafka config, KafkaCustomizations customizations) { + AdminClient adminClient = AdminClient.create(config.getKafkaProps()); + return new TopologyCreator(adminClient, customizations); + } + + @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(); + } + + // Utilities + + public static RCKafkaProps readPropsFromDotEnv(Path path) throws IOException { + String env = Files.readString(path); + String[] split = env.split("\n"); + RCKafkaProps props = new RCKafkaProps(); + for (String s : split) { + if (s.startsWith("#")) { + continue; + } + String[] split1 = s.split("=", 2); + props.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 new file mode 100644 index 00000000..3e638095 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaEventListenerConfig.java @@ -0,0 +1,60 @@ +package org.reactivecommons.async.kafka.config; + +import org.reactivecommons.async.api.DefaultCommandHandler; +import org.reactivecommons.async.api.HandlerRegistry; +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.commons.utils.resolver.HandlerResolverUtil; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.RCAsyncPropsKafka; +import org.reactivecommons.async.kafka.listeners.ApplicationEventListener; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@Configuration +public class RCKafkaEventListenerConfig { + + @Bean + public ApplicationEventListener applicationEventListener(ReactiveMessageListener listener, + HandlerResolver resolver, + MessageConverter messageConverter, + TopologyCreator creator, + DiscardNotifier discardNotifier, + CustomReporter customReporter, + RCAsyncPropsKafka props, + @Value("${spring.application.name}") String appName) { + ApplicationEventListener eventListener = new ApplicationEventListener(listener, + resolver, + messageConverter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + discardNotifier, + customReporter, + appName); + + eventListener.startListener(creator); + + return eventListener; + } + + @Bean + public HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { + final Map registries = context.getBeansOfType(HandlerRegistry.class); + return HandlerResolverUtil.fromHandlerRegistries(registries.values(), defaultCommandHandler); + } + + @Bean + public DefaultCommandHandler defaultCommandHandler() { + return command -> Mono.empty(); + } +} 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 new file mode 100644 index 00000000..d9532cef --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaNotificationEventListenerConfig.java @@ -0,0 +1,60 @@ +package org.reactivecommons.async.kafka.config; + +import org.reactivecommons.async.api.DefaultCommandHandler; +import org.reactivecommons.async.api.HandlerRegistry; +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.commons.utils.resolver.HandlerResolverUtil; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.RCAsyncPropsKafka; +import org.reactivecommons.async.kafka.listeners.ApplicationNotificationsListener; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@Configuration +public class RCKafkaNotificationEventListenerConfig { + + @Bean + public ApplicationNotificationsListener applicationEventListener(ReactiveMessageListener listener, + HandlerResolver resolver, + MessageConverter messageConverter, + TopologyCreator creator, + DiscardNotifier discardNotifier, + CustomReporter customReporter, + RCAsyncPropsKafka props, + @Value("${spring.application.name}") String appName) { + ApplicationNotificationsListener eventListener = new ApplicationNotificationsListener(listener, + resolver, + messageConverter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + discardNotifier, + customReporter, + appName); + + eventListener.startListener(creator); + + return eventListener; + } + + @Bean + public HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { + final Map registries = context.getBeansOfType(HandlerRegistry.class); + return HandlerResolverUtil.fromHandlerRegistries(registries.values(), defaultCommandHandler); + } + + @Bean + public DefaultCommandHandler defaultCommandHandler() { + return command -> Mono.empty(); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java new file mode 100644 index 00000000..b09e7b46 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java @@ -0,0 +1,28 @@ +package org.reactivecommons.async.kafka.config.props; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + + +@Getter +@Setter +@ConfigurationProperties(prefix = "reactive.commons.kafka") +public class RCAsyncPropsKafka { + + @NestedConfigurationProperty + private RCKafkaProps kafkaProps = new RCKafkaProps(); + + /** + * -1 will be considered default value. + * When withDLQRetry is true, it will be retried 10 times. + * When withDLQRetry is false, it will be retried indefinitely. + */ + private Integer maxRetries = -1; + + private Integer retryDelay = 1000; + + private Boolean withDLQRetry = false; + private Boolean createTopology = true; +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java new file mode 100644 index 00000000..9e7e1cd3 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.kafka.config.props; + +import java.util.HashMap; + +public class RCKafkaProps extends HashMap { +} From 2f6e0b0dab4dca8344ebef7204db5eb1e86c8124 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Tue, 20 Aug 2024 22:13:01 +0000 Subject: [PATCH 08/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 12 ++++++++++++ gradle.properties | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1712a9e..e597afae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [v5.0.1-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.1-alpha) (2024-08-20) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.0-alpha...v5.0.1-alpha) + +**Closed issues:** + +- Adjust to reactive-commons library according to the CloudEvents specification. [\#111](https://github.com/reactive-commons/reactive-commons-java/issues/111) + +**Merged pull requests:** + +- chore\(kafka\): Implement kafka binding with support for Domain Events [\#116](https://github.com/reactive-commons/reactive-commons-java/pull/116) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.0.0-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.0-alpha) (2024-08-08) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.4...v5.0.0-alpha) diff --git a/gradle.properties b/gradle.properties index 279b9609..45ae916b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.0-alpha +version=5.0.1-alpha toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter onlyUpdater=true \ No newline at end of file From daf6da69f636e034364400cce9716189bf1369df Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:01:02 -0500 Subject: [PATCH 09/50] chore(next): Unify rabbitmq starter with starter eda (#117) * unify rabbitmq starter, update docs --- .../converters/json/CloudEventBuilderExt.java | 5 + .../listeners/GenericMessageListenerTest.java | 3 - .../async/rabbit/config/RabbitProperties.java | 967 -------- .../async-commons-rabbit-starter.gradle | 17 - .../annotations/EnableCommandListeners.java | 19 - .../annotations/EnableDirectAsyncGateway.java | 19 - .../annotations/EnableDomainEventBus.java | 19 - .../annotations/EnableEventListeners.java | 18 - .../annotations/EnableMessageListeners.java | 26 - .../EnableNotificationListener.java | 19 - .../annotations/EnableQueryListeners.java | 19 - .../rabbit/config/CommandListenersConfig.java | 43 - .../config/DirectAsyncGatewayConfig.java | 63 - .../async/rabbit/config/EventBusConfig.java | 28 - .../rabbit/config/EventListenersConfig.java | 42 - .../config/NotificacionListenersConfig.java | 42 - .../rabbit/config/QueryListenerConfig.java | 44 - .../rabbit/config/RabbitHealthConfig.java | 17 - .../async/rabbit/config/RabbitMqConfig.java | 208 -- .../async/rabbit/config/props/AsyncProps.java | 42 - .../config/props/BrokerConfigProps.java | 77 - .../rabbit/config/props/DirectProps.java | 33 - .../rabbit/config/props/DomainProps.java | 21 - .../rabbit/config/props/EventsProps.java | 28 - .../async/rabbit/config/props/FluxProps.java | 19 - .../rabbit/config/props/GlobalProps.java | 27 - .../health/RabbitReactiveHealthIndicator.java | 35 - .../config/CommandListenersConfigTest.java | 73 - .../config/EventListenersConfigTest.java | 68 - .../NotificacionListenersConfigTest.java | 66 - .../config/QueryListenerConfigTest.java | 67 - .../rabbit/config/RabbitMqConfigTest.java | 67 - .../RabbitReactiveHealthIndicatorTest.java | 71 - .../src/test/resources/application.properties | 1 - .../reactive-commons-eda/1-getting-started.md | 79 - .../3-additional-api-methods.md | 57 - .../4-configuration-properties.md | 94 - .../docs/reactive-commons-eda/_category_.json | 8 - .../reactive-commons/1-getting-started.md | 61 +- docs/docs/reactive-commons/10-wildcards.md | 64 +- .../11-creating-a-cloud-event.md} | 21 +- .../2-sending-a-domain-event.md | 2 +- .../reactive-commons/3-sending-a-command.md | 8 +- .../4-making-an-async-query.md | 6 +- .../9-configuration-properties.md | 117 +- docs/package-lock.json | 1974 +++++++++-------- docs/package.json | 18 +- .../eda-async-receiver-sample.gradle | 2 +- .../eda-async-sender-client-domain-a.gradle | 2 +- .../eda-async-sender-client.gradle | 2 +- .../async-commons-rabbit-standalone.gradle | 0 .../config/DirectAsyncGatewayConfig.java | 0 .../async/rabbit/config/EventBusConfig.java | 0 .../async/rabbit/config/RabbitMqConfig.java | 0 .../async/rabbit/config/RabbitProperties.java | 0 .../async-commons-rabbit-starter.gradle | 0 .../async/RabbitEDADirectAsyncGateway.java | 0 .../annotations/EnableCommandListeners.java | 0 .../annotations/EnableDirectAsyncGateway.java | 0 .../annotations/EnableDomainEventBus.java | 0 .../annotations/EnableEventListeners.java | 0 .../annotations/EnableMessageListeners.java | 0 .../EnableNotificationListener.java | 0 .../annotations/EnableQueryListeners.java | 0 .../rabbit/config/CommandListenersConfig.java | 0 .../rabbit/config/ConnectionManager.java | 0 .../config/DirectAsyncGatewayConfig.java | 0 .../async/rabbit/config/DomainHandlers.java | 0 .../async/rabbit/config/EventBusConfig.java | 0 .../rabbit/config/EventListenersConfig.java | 0 .../rabbit/config/HandlerResolverBuilder.java | 0 .../config/NotificationListenersConfig.java | 0 .../rabbit/config/QueryListenerConfig.java | 0 .../rabbit/config/RabbitHealthConfig.java | 0 .../async/rabbit/config/RabbitMqConfig.java | 11 +- .../async/rabbit/config/RabbitProperties.java | 4 + .../config/RabbitPropertiesAutoConfig.java | 7 + .../rabbit/config/RabbitPropertiesBase.java | 22 +- .../InvalidConfigurationException.java | 0 .../async/rabbit/config/props/AsyncProps.java | 0 .../rabbit/config/props/AsyncPropsDomain.java | 13 +- .../props/AsyncPropsDomainProperties.java | 0 .../config/props/BrokerConfigProps.java | 0 .../rabbit/config/props/DirectProps.java | 0 .../rabbit/config/props/DomainProps.java | 0 .../rabbit/config/props/EventsProps.java | 0 .../async/rabbit/config/props/FluxProps.java | 0 .../rabbit/config/props/GlobalProps.java | 0 .../DomainRabbitReactiveHealthIndicator.java | 0 .../async/rabbit/health/Status.java | 0 .../config/CommandListenersConfigTest.java | 0 .../config/EventListenersConfigTest.java | 0 .../NotificationListenersConfigTest.java | 0 .../config/QueryListenerConfigTest.java | 0 .../rabbit/config/RabbitMqConfigTest.java | 0 ...mainRabbitReactiveHealthIndicatorTest.java | 0 .../src/test/resources/application.properties | 0 97 files changed, 1282 insertions(+), 3603 deletions(-) delete mode 100644 async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java delete mode 100644 async/async-rabbit-starter/async-commons-rabbit-starter.gradle delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java delete mode 100644 async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java delete mode 100644 async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java delete mode 100644 async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java delete mode 100644 async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java delete mode 100644 async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java delete mode 100644 async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java delete mode 100644 async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java delete mode 100644 async/async-rabbit-starter/src/test/resources/application.properties delete mode 100644 docs/docs/reactive-commons-eda/1-getting-started.md delete mode 100644 docs/docs/reactive-commons-eda/3-additional-api-methods.md delete mode 100644 docs/docs/reactive-commons-eda/4-configuration-properties.md delete mode 100644 docs/docs/reactive-commons-eda/_category_.json rename docs/docs/{reactive-commons-eda/2-creating-a-cloud-event.md => reactive-commons/11-creating-a-cloud-event.md} (58%) rename {async => starters}/async-rabbit-standalone/async-commons-rabbit-standalone.gradle (100%) rename {async => starters}/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java (100%) rename {async => starters}/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java (100%) rename {async => starters}/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java (100%) rename {async => starters}/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java (100%) rename async/async-rabbit-starter-eda/async-commons-rabbit-starter-eda.gradle => starters/async-rabbit-starter/async-commons-rabbit-starter.gradle (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java (96%) create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java rename async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java => starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesBase.java (98%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java (90%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/main/java/org/reactivecommons/async/rabbit/health/Status.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java (100%) rename {async/async-rabbit-starter-eda => starters/async-rabbit-starter}/src/test/resources/application.properties (100%) diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java index 6a80cb7b..8ceb7b0d 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @@ -16,6 +17,10 @@ public static byte[] asBytes(Object object) { return mapper.writeValueAsBytes(object); } + public static CloudEventData asCloudEventData(Object object) { + return () -> asBytes(object); + } + @SneakyThrows public static T fromCloudEventData(CloudEvent cloudEvent, Class classValue) { return mapper.readValue(Objects.requireNonNull(cloudEvent.getData()).toBytes(), classValue); diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java index fb7b23b7..1025ad60 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java @@ -72,8 +72,6 @@ void shouldStartListener() { Headers header = new RecordHeaders().add("contentType", "application/json".getBytes(StandardCharsets.UTF_8)); when(record.headers()).thenReturn(header); when(record.key()).thenReturn("key"); - ReceiverOffset receiverOffset = mock(ReceiverOffset.class); - when(record.receiverOffset()).thenReturn(receiverOffset); Flux> flux = Flux.just(record); when(receiver.listen(anyString(), any(List.class))).thenReturn(flux); @@ -91,7 +89,6 @@ void shouldStartListener() { StepVerifier.create(flow).expectNext("").verifyComplete(); // Assert verify(topologyCreator, times(1)).createTopics(any(List.class)); - verify(receiverOffset, atLeastOnce()).acknowledge(); } diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java deleted file mode 100644 index 1ed9934e..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java +++ /dev/null @@ -1,967 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.convert.DurationUnit; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@ConfigurationProperties(prefix = "spring.rabbitmq") -public class RabbitProperties { - - /** - * RabbitMQ host. - */ - private String host = "localhost"; - - /** - * RabbitMQ port. - */ - private int port = 5672; - - /** - * Login user to authenticate to the communications. - */ - private String username = "guest"; - - /** - * Login to authenticate against the communications. - */ - private String password = "guest"; - - /** - * SSL configuration. - */ - private final Ssl ssl = new Ssl(); - - /** - * Virtual host to use when connecting to the communications. - */ - private String virtualHost; - - /** - * Comma-separated list of addresses to which the client should connect. - */ - private String addresses; - - /** - * Requested heartbeat timeout; zero for none. If a duration suffix is not specified, - * seconds will be used. - */ - @DurationUnit(ChronoUnit.SECONDS) - private Duration requestedHeartbeat; - - /** - * Whether to enable publisher confirms. - */ - private boolean publisherConfirms; - - /** - * Whether to enable publisher returns. - */ - private boolean publisherReturns; - - /** - * Connection timeout. Set it to zero to wait forever. - */ - private Duration connectionTimeout; - - /** - * Cache configuration. - */ - private final Cache cache = new Cache(); - - /** - * Listener container configuration. - */ - private final Listener listener = new Listener(); - - private final Template template = new Template(); - - private List
parsedAddresses; - - public String getHost() { - return this.host; - } - - /** - * Returns the host from the first address, or the configured host if no addresses - * have been set. - * @return the host - * @see #setAddresses(String) - * @see #getHost() - */ - public String determineHost() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getHost(); - } - return this.parsedAddresses.get(0).host; - } - - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return this.port; - } - - /** - * Returns the port from the first address, or the configured port if no addresses - * have been set. - * @return the port - * @see #setAddresses(String) - * @see #getPort() - */ - public int determinePort() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getPort(); - } - Address address = this.parsedAddresses.get(0); - return address.port; - } - - public void setPort(int port) { - this.port = port; - } - - public String getAddresses() { - return this.addresses; - } - - /** - * Returns the comma-separated addresses or a single address ({@code host:port}) - * created from the configured host and port if no addresses have been set. - * @return the addresses - */ - public String determineAddresses() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return this.host + ":" + this.port; - } - List addressStrings = new ArrayList<>(); - for (Address parsedAddress : this.parsedAddresses) { - addressStrings.add(parsedAddress.host + ":" + parsedAddress.port); - } - return StringUtils.collectionToCommaDelimitedString(addressStrings); - } - - public void setAddresses(String addresses) { - this.addresses = addresses; - this.parsedAddresses = parseAddresses(addresses); - } - - private List
parseAddresses(String addresses) { - List
parsedAddresses = new ArrayList<>(); - for (String address : StringUtils.commaDelimitedListToStringArray(addresses)) { - parsedAddresses.add(new Address(address)); - } - return parsedAddresses; - } - - public String getUsername() { - return this.username; - } - - /** - * If addresses have been set and the first address has a username it is returned. - * Otherwise returns the result of calling {@code getUsername()}. - * @return the username - * @see #setAddresses(String) - * @see #getUsername() - */ - public String determineUsername() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return this.username; - } - Address address = this.parsedAddresses.get(0); - return (address.username != null) ? address.username : this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - - /** - * If addresses have been set and the first address has a password it is returned. - * Otherwise returns the result of calling {@code getPassword()}. - * @return the password or {@code null} - * @see #setAddresses(String) - * @see #getPassword() - */ - public String determinePassword() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getPassword(); - } - Address address = this.parsedAddresses.get(0); - return (address.password != null) ? address.password : getPassword(); - } - - public void setPassword(String password) { - this.password = password; - } - - public Ssl getSsl() { - return this.ssl; - } - - public String getVirtualHost() { - return this.virtualHost; - } - - /** - * If addresses have been set and the first address has a virtual host it is returned. - * Otherwise returns the result of calling {@code getVirtualHost()}. - * @return the virtual host or {@code null} - * @see #setAddresses(String) - * @see #getVirtualHost() - */ - public String determineVirtualHost() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getVirtualHost(); - } - Address address = this.parsedAddresses.get(0); - return (address.virtualHost != null) ? address.virtualHost : getVirtualHost(); - } - - public void setVirtualHost(String virtualHost) { - this.virtualHost = "".equals(virtualHost) ? "/" : virtualHost; - } - - public Duration getRequestedHeartbeat() { - return this.requestedHeartbeat; - } - - public void setRequestedHeartbeat(Duration requestedHeartbeat) { - this.requestedHeartbeat = requestedHeartbeat; - } - - public boolean isPublisherConfirms() { - return this.publisherConfirms; - } - - public void setPublisherConfirms(boolean publisherConfirms) { - this.publisherConfirms = publisherConfirms; - } - - public boolean isPublisherReturns() { - return this.publisherReturns; - } - - public void setPublisherReturns(boolean publisherReturns) { - this.publisherReturns = publisherReturns; - } - - public Duration getConnectionTimeout() { - return this.connectionTimeout; - } - - public void setConnectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public Cache getCache() { - return this.cache; - } - - public Listener getListener() { - return this.listener; - } - - public Template getTemplate() { - return this.template; - } - - public static class Ssl { - - /** - * Whether to enable SSL support. - */ - private boolean enabled; - - /** - * Path to the key store that holds the SSL certificate. - */ - private String keyStore; - - /** - * Key store type. - */ - private String keyStoreType = "PKCS12"; - - /** - * Password used to access the key store. - */ - private String keyStorePassword; - - /** - * Trust store that holds SSL certificates. - */ - private String trustStore; - - /** - * Trust store type. - */ - private String trustStoreType = "JKS"; - - /** - * Password used to access the trust store. - */ - private String trustStorePassword; - - /** - * SSL algorithm to use. By default, configured by the Rabbit client library. - */ - private String algorithm; - - /** - * Whether to enable server side certificate validation. - */ - private boolean validateServerCertificate = true; - - /** - * Whether to enable hostname verification. - */ - private boolean verifyHostname = true; - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getKeyStore() { - return this.keyStore; - } - - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStoreType() { - return this.keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStorePassword() { - return this.keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getTrustStore() { - return this.trustStore; - } - - public void setTrustStore(String trustStore) { - this.trustStore = trustStore; - } - - public String getTrustStoreType() { - return this.trustStoreType; - } - - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - public String getTrustStorePassword() { - return this.trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public String getAlgorithm() { - return this.algorithm; - } - - public void setAlgorithm(String sslAlgorithm) { - this.algorithm = sslAlgorithm; - } - - public boolean isValidateServerCertificate() { - return this.validateServerCertificate; - } - - public void setValidateServerCertificate(boolean validateServerCertificate) { - this.validateServerCertificate = validateServerCertificate; - } - - public boolean getVerifyHostname() { - return this.verifyHostname; - } - - public void setVerifyHostname(boolean verifyHostname) { - this.verifyHostname = verifyHostname; - } - - } - - public static class Cache { - - private final Cache.Channel channel = new Cache.Channel(); - - private final Cache.Connection connection = new Cache.Connection(); - - public Cache.Channel getChannel() { - return this.channel; - } - - public Cache.Connection getConnection() { - return this.connection; - } - - public static class Channel { - - /** - * Number of channels to retain in the cache. When "check-timeout" > 0, max - * channels per connection. - */ - private Integer size; - - /** - * Duration to wait to obtain a channel if the cache size has been reached. If - * 0, always create a new channel. - */ - private Duration checkoutTimeout; - - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - - public Duration getCheckoutTimeout() { - return this.checkoutTimeout; - } - - public void setCheckoutTimeout(Duration checkoutTimeout) { - this.checkoutTimeout = checkoutTimeout; - } - - } - - public static class Connection { - - /** - * Connection factory cache mode. - */ - private String mode = "CHANNEL"; - - /** - * Number of connections to cache. Only applies when mode is CONNECTION. - */ - private Integer size; - - public String getMode() { - return this.mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - - } - - } - - public enum ContainerType { - - /** - * Container where the RabbitMQ consumer dispatches messages to an invoker thread. - */ - SIMPLE, - - /** - * Container where the listener is invoked directly on the RabbitMQ consumer - * thread. - */ - DIRECT - - } - - public static class Listener { - - /** - * Listener container type. - */ - private ContainerType type = ContainerType.SIMPLE; - - private final SimpleContainer simple = new SimpleContainer(); - - private final DirectContainer direct = new DirectContainer(); - - public ContainerType getType() { - return this.type; - } - - public void setType(ContainerType containerType) { - this.type = containerType; - } - - public SimpleContainer getSimple() { - return this.simple; - } - - public DirectContainer getDirect() { - return this.direct; - } - - } - - public abstract static class AmqpContainer { - - /** - * Whether to start the container automatically on startup. - */ - private boolean autoStartup = true; - - /** - * Acknowledge mode of container. - */ - private Integer acknowledgeMode; - - /** - * Maximum number of unacknowledged messages that can be outstanding at each - * consumer. - */ - private Integer prefetch; - - /** - * Whether rejected deliveries are re-queued by default. - */ - private Boolean defaultRequeueRejected; - - /** - * How often idle container events should be published. - */ - private Duration idleEventInterval; - - /** - * Optional properties for a retry interceptor. - */ - private final ListenerRetry retry = new ListenerRetry(); - - public boolean isAutoStartup() { - return this.autoStartup; - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public Integer getAcknowledgeMode() { - return this.acknowledgeMode; - } - - public void setAcknowledgeMode(Integer acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; - } - - public Integer getPrefetch() { - return this.prefetch; - } - - public void setPrefetch(Integer prefetch) { - this.prefetch = prefetch; - } - - public Boolean getDefaultRequeueRejected() { - return this.defaultRequeueRejected; - } - - public void setDefaultRequeueRejected(Boolean defaultRequeueRejected) { - this.defaultRequeueRejected = defaultRequeueRejected; - } - - public Duration getIdleEventInterval() { - return this.idleEventInterval; - } - - public void setIdleEventInterval(Duration idleEventInterval) { - this.idleEventInterval = idleEventInterval; - } - - public abstract boolean isMissingQueuesFatal(); - - public ListenerRetry getRetry() { - return this.retry; - } - - } - - /** - * Configuration properties for {@code SimpleMessageListenerContainer}. - */ - public static class SimpleContainer extends AmqpContainer { - - /** - * Minimum number of listener invoker threads. - */ - private Integer concurrency; - - /** - * Maximum number of listener invoker threads. - */ - private Integer maxConcurrency; - - /** - * Number of messages to be processed between acks when the acknowledge mode is - * AUTO. If larger than prefetch, prefetch will be increased to this value. - */ - private Integer transactionSize; - - /** - * Whether to fail if the queues declared by the container are not available on - * the communications and/or whether to stop the container if one or more queues are - * deleted at runtime. - */ - private boolean missingQueuesFatal = true; - - public Integer getConcurrency() { - return this.concurrency; - } - - public void setConcurrency(Integer concurrency) { - this.concurrency = concurrency; - } - - public Integer getMaxConcurrency() { - return this.maxConcurrency; - } - - public void setMaxConcurrency(Integer maxConcurrency) { - this.maxConcurrency = maxConcurrency; - } - - public Integer getTransactionSize() { - return this.transactionSize; - } - - public void setTransactionSize(Integer transactionSize) { - this.transactionSize = transactionSize; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - - } - - /** - * Configuration properties for {@code DirectMessageListenerContainer}. - */ - public static class DirectContainer extends AmqpContainer { - - /** - * Number of consumers per queue. - */ - private Integer consumersPerQueue; - - /** - * Whether to fail if the queues declared by the container are not available on - * the communications. - */ - private boolean missingQueuesFatal = false; - - public Integer getConsumersPerQueue() { - return this.consumersPerQueue; - } - - public void setConsumersPerQueue(Integer consumersPerQueue) { - this.consumersPerQueue = consumersPerQueue; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - - } - - public static class Template { - - private final Retry retry = new Retry(); - - /** - * Whether to enable mandatory messages. - */ - private Boolean mandatory; - - /** - * Timeout for `receive()` operations. - */ - private Duration receiveTimeout; - - /** - * Timeout for `sendAndReceive()` operations. - */ - private Duration replyTimeout; - - /** - * Name of the default exchange to use for send operations. - */ - private String exchange = ""; - - /** - * Value of a default routing key to use for send operations. - */ - private String routingKey = ""; - - /** - * Name of the default queue to receive messages from when none is specified - * explicitly. - */ - private String queue; - - public Retry getRetry() { - return this.retry; - } - - public Boolean getMandatory() { - return this.mandatory; - } - - public void setMandatory(Boolean mandatory) { - this.mandatory = mandatory; - } - - public Duration getReceiveTimeout() { - return this.receiveTimeout; - } - - public void setReceiveTimeout(Duration receiveTimeout) { - this.receiveTimeout = receiveTimeout; - } - - public Duration getReplyTimeout() { - return this.replyTimeout; - } - - public void setReplyTimeout(Duration replyTimeout) { - this.replyTimeout = replyTimeout; - } - - public String getExchange() { - return this.exchange; - } - - public void setExchange(String exchange) { - this.exchange = exchange; - } - - public String getRoutingKey() { - return this.routingKey; - } - - public void setRoutingKey(String routingKey) { - this.routingKey = routingKey; - } - - public String getQueue() { - return this.queue; - } - - public void setQueue(String queue) { - this.queue = queue; - } - - } - - public static class Retry { - - /** - * Whether publishing retries are enabled. - */ - private boolean enabled; - - /** - * Maximum number of attempts to deliver a message. - */ - private int maxAttempts = 3; - - /** - * Duration between the first and second attempt to deliver a message. - */ - private Duration initialInterval = Duration.ofMillis(1000); - - /** - * Multiplier to apply to the previous retry interval. - */ - private double multiplier = 1.0; - - /** - * Maximum duration between attempts. - */ - private Duration maxInterval = Duration.ofMillis(10000); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public int getMaxAttempts() { - return this.maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public Duration getInitialInterval() { - return this.initialInterval; - } - - public void setInitialInterval(Duration initialInterval) { - this.initialInterval = initialInterval; - } - - public double getMultiplier() { - return this.multiplier; - } - - public void setMultiplier(double multiplier) { - this.multiplier = multiplier; - } - - public Duration getMaxInterval() { - return this.maxInterval; - } - - public void setMaxInterval(Duration maxInterval) { - this.maxInterval = maxInterval; - } - - } - - public static class ListenerRetry extends Retry { - - /** - * Whether retries are stateless or stateful. - */ - private boolean stateless = true; - - public boolean isStateless() { - return this.stateless; - } - - public void setStateless(boolean stateless) { - this.stateless = stateless; - } - - } - - private static final class Address { - - private static final String PREFIX_AMQP = "amqp://"; - - private static final int DEFAULT_PORT = 5672; - - private String host; - - private int port; - - private String username; - - private String password; - - private String virtualHost; - - private Address(String input) { - input = input.trim(); - input = trimPrefix(input); - input = parseUsernameAndPassword(input); - input = parseVirtualHost(input); - parseHostAndPort(input); - } - - private String trimPrefix(String input) { - if (input.startsWith(PREFIX_AMQP)) { - input = input.substring(PREFIX_AMQP.length()); - } - return input; - } - - private String parseUsernameAndPassword(String input) { - if (input.contains("@")) { - String[] split = StringUtils.split(input, "@"); - if (split == null) return input; - String creds = split[0]; - input = split[1]; - split = StringUtils.split(creds, ":"); - if (split != null) { - this.username = split[0]; - if (split.length > 1) { - this.password = split[1]; - } - } - } - return input; - } - - private String parseVirtualHost(String input) { - int hostIndex = input.indexOf('/'); - if (hostIndex >= 0) { - this.virtualHost = input.substring(hostIndex + 1); - if (this.virtualHost.isEmpty()) { - this.virtualHost = "/"; - } - input = input.substring(0, hostIndex); - } - return input; - } - - private void parseHostAndPort(String input) { - int portIndex = input.indexOf(':'); - if (portIndex == -1) { - this.host = input; - this.port = DEFAULT_PORT; - } - else { - this.host = input.substring(0, portIndex); - this.port = Integer.valueOf(input.substring(portIndex + 1)); - } - } - - } - -} diff --git a/async/async-rabbit-starter/async-commons-rabbit-starter.gradle b/async/async-rabbit-starter/async-commons-rabbit-starter.gradle deleted file mode 100644 index d2b89d6c..00000000 --- a/async/async-rabbit-starter/async-commons-rabbit-starter.gradle +++ /dev/null @@ -1,17 +0,0 @@ -ext { - artifactId = 'async-commons-rabbit-starter' - artifactDescription = 'Async Commons Starter' -} - -dependencies { - api project(':async-rabbit') - implementation 'io.projectreactor.rabbitmq:reactor-rabbitmq' - - compileOnly 'org.springframework.boot:spring-boot-starter' - compileOnly 'org.springframework.boot:spring-boot-starter-actuator' - - 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/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java deleted file mode 100644 index fcbe9833..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(CommandListenersConfig.class) -@Configuration -public @interface EnableCommandListeners { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java deleted file mode 100644 index 359913fd..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.DirectAsyncGatewayConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(DirectAsyncGatewayConfig.class) -@Configuration -public @interface EnableDirectAsyncGateway { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java deleted file mode 100644 index 6820ab50..00000000 --- a/async/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/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java deleted file mode 100644 index 87791c20..00000000 --- a/async/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/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java deleted file mode 100644 index 51aebd4b..00000000 --- a/async/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.NotificacionListenersConfig; -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, NotificacionListenersConfig.class}) -@Configuration -public @interface EnableMessageListeners { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java deleted file mode 100644 index 66f97f02..00000000 --- a/async/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.NotificacionListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(NotificacionListenersConfig.class) -@Configuration -public @interface EnableNotificationListener { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java deleted file mode 100644 index 6eb878b0..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(QueryListenerConfig.class) -@Configuration -public @interface EnableQueryListeners { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java deleted file mode 100644 index e5db6229..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -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.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class CommandListenersConfig { - - @Value("${spring.application.name}") - private String appName; - - private final AsyncProps asyncProps; - - @Bean - public ApplicationCommandListener applicationCommandListener(ReactiveMessageListener listener, - HandlerResolver resolver, MessageConverter converter, - DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, - CustomReporter errorReporter) { - ApplicationCommandListener commandListener = new ApplicationCommandListener(listener, - brokerConfigProps.getCommandsQueue(), resolver, - brokerConfigProps.getDirectMessagesExchangeName(), converter, asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), asyncProps.getDelayedCommands(), asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), asyncProps.getDirect().getMaxLengthBytes(), discardNotifier, errorReporter); - - commandListener.startListener(); - - return commandListener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java deleted file mode 100644 index cb85862c..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java +++ /dev/null @@ -1,63 +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.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -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.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -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.logging.Level; - -@Log -@Configuration -@Import(RabbitMqConfig.class) -@RequiredArgsConstructor -public class DirectAsyncGatewayConfig { - - private final IBrokerConfigProps brokerConfigProps; - - @Bean - public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, - ReactiveMessageSender rSender, MessageConverter converter, - MeterRegistry meterRegistry) { - return new RabbitDirectAsyncGateway(config, router, rSender, brokerConfigProps.getDirectMessagesExchangeName(), - converter, meterRegistry); - } - - @Bean - public ApplicationReplyListener msgListener(ReactiveReplyRouter router, BrokerConfig config, AsyncProps asyncProps, - ReactiveMessageListener listener) { - if (!asyncProps.isListenReplies()) { - log.log(Level.WARNING, "ApplicationReplyListener is disabled in AsyncProps or app.async.listenReplies"); - return null; - } - final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, listener, - brokerConfigProps.getReplyQueue(), brokerConfigProps.getGlobalReplyExchangeName(), - asyncProps.getCreateTopology()); - replyListener.startListening(config.getRoutingKey()); - return replyListener; - } - - @Bean - public ReactiveReplyRouter router() { - return new ReactiveReplyRouter(); - } - - @Bean - @ConditionalOnMissingBean(MeterRegistry.class) - public MeterRegistry defaultRabbitMeterRegistry() { - return new SimpleMeterRegistry(); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java deleted file mode 100644 index 836655ac..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java +++ /dev/null @@ -1,28 +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.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Configuration -@Import(RabbitMqConfig.class) -public class EventBusConfig { - - @Bean - public DomainEventBus domainEventBus(ReactiveMessageSender sender, IBrokerConfigProps props, AsyncProps asyncProps, - BrokerConfig config) { - final String exchangeName = props.getDomainEventsExchangeName(); - if (asyncProps.getCreateTopology()) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); - } - return new RabbitDomainEventBus(sender, exchangeName, config); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java deleted file mode 100644 index 2cd3477f..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -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.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class EventListenersConfig { - - @Value("${spring.application.name}") - private String appName; - - private final AsyncProps asyncProps; - - @Bean - public ApplicationEventListener eventListener(HandlerResolver resolver, MessageConverter messageConverter, - ReactiveMessageListener receiver, DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, CustomReporter errorReporter) { - - final ApplicationEventListener listener = new ApplicationEventListener(receiver, - brokerConfigProps.getEventsQueue(), brokerConfigProps.getDomainEventsExchangeName(), resolver, - messageConverter, asyncProps.getWithDLQRetry(), asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), asyncProps.getRetryDelay(), - asyncProps.getDomain().getEvents().getMaxLengthBytes(), discardNotifier, errorReporter, appName); - - listener.startListener(); - - return listener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java deleted file mode 100644 index 265f5b32..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -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.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -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; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class NotificacionListenersConfig { - - private final AsyncProps asyncProps; - - @Bean - public ApplicationNotificationListener eventNotificationListener(HandlerResolver resolver, - MessageConverter messageConverter, - ReactiveMessageListener receiver, - DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, - CustomReporter errorReporter) { - final ApplicationNotificationListener listener = new ApplicationNotificationListener( - receiver, - brokerConfigProps.getDomainEventsExchangeName(), - brokerConfigProps.getNotificationsQueue(), - asyncProps.getCreateTopology(), - resolver, - messageConverter, - discardNotifier, - errorReporter); - listener.startListener(); - return listener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java deleted file mode 100644 index fa01ab6d..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; -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.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class QueryListenerConfig { - - @Value("${spring.application.name}") - private String appName; - - private final AsyncProps asyncProps; - - @Bean - public ApplicationQueryListener queryListener(MessageConverter converter, HandlerResolver resolver, - ReactiveMessageSender sender, ReactiveMessageListener rlistener, - DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, - CustomReporter errorReporter) { - final ApplicationQueryListener listener = new ApplicationQueryListener(rlistener, - brokerConfigProps.getQueriesQueue(), resolver, sender, brokerConfigProps.getDirectMessagesExchangeName(), converter, - brokerConfigProps.getGlobalReplyExchangeName(), asyncProps.getWithDLQRetry(), asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), asyncProps.getRetryDelay(), asyncProps.getGlobal().getMaxLengthBytes(), - asyncProps.getDirect().isDiscardTimeoutQueries(), discardNotifier, errorReporter); - - listener.startListener(); - - return listener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java deleted file mode 100644 index cdef9c85..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; -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; - -@Configuration -@ConditionalOnClass(AbstractReactiveHealthIndicator.class) -public class RabbitHealthConfig { - - @Bean - public RabbitReactiveHealthIndicator rabbitHealthIndicator(ConnectionFactoryProvider provider) { - return new RabbitReactiveHealthIndicator(provider); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java deleted file mode 100644 index e63771bd..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ /dev/null @@ -1,208 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.api.domain.DomainEventBus; -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.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.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.commons.utils.resolver.HandlerResolverUtil; -import org.reactivecommons.async.rabbit.DynamicRegistryImp; -import org.reactivecommons.async.commons.DLQDiscardNotifier; -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.BrokerConfigProps; -import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; -import org.springframework.beans.factory.annotation.Value; -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.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.time.Duration; -import java.util.Map; -import java.util.logging.Level; - -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Log -@Configuration -@RequiredArgsConstructor -@EnableConfigurationProperties({ - RabbitProperties.class, - AsyncProps.class -}) -@Import({BrokerConfigProps.class, RabbitHealthConfig.class}) -public class RabbitMqConfig { - - private static final String LISTENER_TYPE = "listener"; - - private static final String SENDER_TYPE = "sender"; - - private final AsyncProps asyncProps; - - @Value("${spring.application.name}") - private String appName; - - @Bean - public ReactiveMessageSender messageSender(MessageConverter converter, BrokerConfigProps brokerConfigProps, SenderOptions senderOptions) { - final Sender sender = RabbitFlux.createSender(senderOptions); - return new ReactiveMessageSender(sender, brokerConfigProps.getAppName(), converter, new TopologyCreator(sender)); - } - - @Bean - public SenderOptions reactiveCommonsSenderOptions(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)); - } - - @Bean - public ReactiveMessageListener messageListener(ConnectionFactoryProvider provider) { - final Mono connection = - createConnectionMono(provider.getConnectionFactory(), appName, 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), - asyncProps.getFlux().getMaxConcurrency(), - asyncProps.getPrefetchCount()); - } - - @Bean - @ConditionalOnMissingBean - public BrokerConfig brokerConfig() { - return new BrokerConfig(); - } - - @Bean - @ConditionalOnMissingBean - public ConnectionFactoryProvider rabbitRConnectionFactory(RabbitProperties properties) - throws NoSuchAlgorithmException, KeyManagementException { - 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 ObjectMapperSupplier objectMapperSupplier() { - return new DefaultObjectMapperSupplier(); - } - - @Bean - @ConditionalOnMissingBean - public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); - } - - @Bean - @ConditionalOnMissingBean - public DiscardNotifier rabbitDiscardNotifier(MessageConverter messageConverter, AsyncProps asyncProps, - ReactiveMessageSender sender, BrokerConfigProps props) { - return new DLQDiscardNotifier(domainEventBus(sender, props, asyncProps.getCreateTopology()), messageConverter); - } - - @Bean - @ConditionalOnMissingBean - public CustomReporter reactiveCommonsCustomErrorReporter() { - return new DefaultCustomReporter(); - } - - private DomainEventBus domainEventBus(ReactiveMessageSender sender, BrokerConfigProps props, boolean createExchange) { - final String exchangeName = props.getDomainEventsExchangeName(); - if (createExchange) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); - } - return new RabbitDomainEventBus(sender, exchangeName); - } - - 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 HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { - final Map registries = context.getBeansOfType(HandlerRegistry.class); - return HandlerResolverUtil.fromHandlerRegistries(registries.values(), defaultCommandHandler); - } - - @Bean - public DynamicRegistry dynamicRegistry(HandlerResolver resolver, ReactiveMessageListener listener, IBrokerConfigProps props) { - return new DynamicRegistryImp(resolver, listener.getTopologyCreator(), props); - } - - @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")); - } - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java deleted file mode 100644 index e7f22098..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - - -@Getter -@Setter -@ConfigurationProperties(prefix = "app.async") -public class AsyncProps { - - @NestedConfigurationProperty - private FluxProps flux = new FluxProps(); - - @NestedConfigurationProperty - private DomainProps domain = new DomainProps(); - - @NestedConfigurationProperty - private DirectProps direct = new DirectProps(); - - @NestedConfigurationProperty - private GlobalProps global = new GlobalProps(); - - private boolean listenReplies = true; - - /** - * -1 will be considered default value. - * When withDLQRetry is true, it will be retried 10 times. - * When withDLQRetry is false, it will be retried indefinitely. - */ - private Integer maxRetries = -1; - - private Integer prefetchCount = 250; - - private Integer retryDelay = 1000; - - private Boolean withDLQRetry = false; - private Boolean createTopology = true; // auto delete queues will always be created and bound - private Boolean delayedCommands = false; -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java deleted file mode 100644 index 77bcaff9..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.Getter; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.springframework.beans.factory.annotation.Value; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.reactivecommons.async.commons.utils.NameGenerator.fromNameWithSuffix; -import static org.reactivecommons.async.commons.utils.NameGenerator.generateNameFrom; - - -@Getter -public class BrokerConfigProps implements IBrokerConfigProps { - private final String appName; - private final AsyncProps asyncProps; - private final AtomicReference replyQueueName = new AtomicReference<>(); - private final AtomicReference notificationsQueueName = new AtomicReference<>(); - - public BrokerConfigProps(@Value("${spring.application.name}") String appName, AsyncProps asyncProps) { - this.appName = appName; - this.asyncProps = asyncProps; - } - - @Override - public String getEventsQueue() { - return fromNameWithSuffix(getAppName(), asyncProps.getDomain().getEvents().getEventsSuffix()); - } - - @Override - public String getNotificationsQueue() { - return resolveTemporaryQueue(notificationsQueueName, asyncProps.getDomain().getEvents().getNotificationSuffix()); - } - - @Override - public String getQueriesQueue() { - return fromNameWithSuffix(getAppName(), asyncProps.getDirect().getQuerySuffix()); - } - - @Override - public String getCommandsQueue() { - return fromNameWithSuffix(getAppName(), asyncProps.getDirect().getCommandSuffix()); - } - - @Override - public String getReplyQueue() { - return resolveTemporaryQueue(replyQueueName, asyncProps.getGlobal().getRepliesSuffix()); - } - - @Override - public String getDomainEventsExchangeName() { - return asyncProps.getDomain().getEvents().getExchange(); - } - - @Override - public String getDirectMessagesExchangeName() { - return asyncProps.getDirect().getExchange(); - } - - @Override - public String getGlobalReplyExchangeName() { - return asyncProps.getGlobal().getExchange(); - } - - private String resolveTemporaryQueue(AtomicReference property, String suffix) { - final String name = property.get(); - if (name == null) { - final String replyName = generateNameFrom(getAppName(), suffix); - if (property.compareAndSet(null, replyName)) { - return replyName; - } else { - return property.get(); - } - } - return name; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java deleted file mode 100644 index 011b319a..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Optional; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class DirectProps { - - @Builder.Default - private String exchange = "directMessages"; - - @Builder.Default - private String querySuffix = "query"; - - @Builder.Default - private String commandSuffix = ""; - - @Builder.Default - private Optional maxLengthBytes = Optional.empty(); - - @Builder.Default - private boolean discardTimeoutQueries = false; - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java deleted file mode 100644 index 4bc5fb1e..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class DomainProps { - - @NestedConfigurationProperty - @Builder.Default - private EventsProps events = new EventsProps(); - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java deleted file mode 100644 index 72ed1f23..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Optional; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class EventsProps { - - @Builder.Default - private String exchange = "domainEvents"; - @Builder.Default - private String eventsSuffix = "subsEvents"; - @Builder.Default - private String notificationSuffix = "notification"; - - @Builder.Default - private Optional maxLengthBytes = Optional.empty(); - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java deleted file mode 100644 index 33162aa2..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class FluxProps { - - @Builder.Default - private Integer maxConcurrency = 250; - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java deleted file mode 100644 index 33c2451d..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Optional; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class GlobalProps { - - @Builder.Default - private String exchange = "globalReply"; - - @Builder.Default - private String repliesSuffix = "replies"; - - @Builder.Default - private Optional maxLengthBytes = Optional.empty(); - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java deleted file mode 100644 index ddb38a8a..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import reactor.core.publisher.Mono; - -@RequiredArgsConstructor -public class RabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - private static final String VERSION = "version"; - private final ConnectionFactoryProvider provider; - - @Override - protected Mono doHealthCheck(Health.Builder builder) { - return Mono.defer(this::getVersion) - .map(version -> builder.up().withDetail(VERSION, version).build()); - } - - private Mono getVersion() { - return Mono.just(provider) - .map(ConnectionFactoryProvider::getConnectionFactory) - .map(this::getRawVersion); - } - - @SneakyThrows - private String getRawVersion(ConnectionFactory factory) { - try (Connection connection = factory.newConnection()) { - return connection.getServerProperties().get(VERSION).toString(); - } - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java deleted file mode 100644 index d171d1a5..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.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.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.commons.DiscardNotifier; -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.commons.HandlerResolver; -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.BrokerConfigProps; -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 java.lang.reflect.Field; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class CommandListenersConfigTest { - - - private final AsyncProps props = new AsyncProps(); - private CommandListenersConfig config = new CommandListenersConfig(props); - 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 IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @BeforeEach - public void init() throws NoSuchFieldException, IllegalAccessException { - final Field appName = CommandListenersConfig.class.getDeclaredField("appName"); - appName.setAccessible(true); - appName.set(config, "queue"); - 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); - } - - @Test - void applicationCommandListener() { - final ApplicationCommandListener commandListener = config.applicationCommandListener( - listener, - handlerResolver, - messageConverter, - discardNotifier, - brokerConfigProps, - customReporter - ); - Assertions.assertThat(commandListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java deleted file mode 100644 index a939a666..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java +++ /dev/null @@ -1,68 +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.config.IBrokerConfigProps; -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 org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.*; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class EventListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final EventListenersConfig config = new EventListenersConfig(props); - 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 IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @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); - } - - @Test - void eventListener() { - final ApplicationEventListener eventListener = config.eventListener( - handlerResolver, - messageConverter, - listener, - discardNotifier, - brokerConfigProps, - customReporter - ); - - Assertions.assertThat(eventListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java deleted file mode 100644 index ef070444..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java +++ /dev/null @@ -1,66 +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.config.IBrokerConfigProps; -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 org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -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; - -class NotificacionListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final NotificacionListenersConfig config = new NotificacionListenersConfig(props); - 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 IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @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); - } - - @Test - void eventNotificationListener() { - final ApplicationNotificationListener applicationEventListener = config. - eventNotificationListener(handlerResolver, messageConverter, listener, discardNotifier, brokerConfigProps, customReporter); - Assertions.assertThat(applicationEventListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java deleted file mode 100644 index 4806296a..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.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.reactivecommons.async.commons.DiscardNotifier; -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.commons.HandlerResolver; -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.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; - -class QueryListenerConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final QueryListenerConfig config = new QueryListenerConfig(props); - 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 ReactiveMessageSender sender = mock(ReactiveMessageSender.class); - private final IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @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); - } - - @Test - void queryListener() { - final ApplicationQueryListener queryListener = config.queryListener(messageConverter, handlerResolver, sender, - listener, discardNotifier, brokerConfigProps, customReporter); - Assertions.assertThat(queryListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java deleted file mode 100644 index 33ca98f9..00000000 --- a/async/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(null); - - @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/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java deleted file mode 100644 index 2d173758..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.BeforeEach; -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.async.rabbit.config.ConnectionFactoryProvider; -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 java.io.IOException; -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.when; - -@ExtendWith(MockitoExtension.class) -public class RabbitReactiveHealthIndicatorTest { - @Mock - private ConnectionFactoryProvider provider; - @Mock - private ConnectionFactory factory; - @Mock - private Connection connection; - @InjectMocks - private RabbitReactiveHealthIndicator indicator; - - @BeforeEach - void setup() { - when(provider.getConnectionFactory()).thenReturn(factory); - } - - @Test - void shouldBeUp() throws IOException, TimeoutException { - // Arrange - Map properties = new TreeMap<>(); - properties.put("version", "1.2.3"); - when(factory.newConnection()).thenReturn(connection); - when(connection.getServerProperties()).thenReturn(properties); - // Act - Mono result = indicator.doHealthCheck(new Builder()); - // Assert - StepVerifier.create(result) - .assertNext(health -> { - assertEquals("1.2.3", health.getDetails().get("version")); - assertEquals(Status.UP, health.getStatus()); - }) - .verifyComplete(); - } - - @Test - void shouldBeDown() throws IOException, TimeoutException { - // Arrange - when(factory.newConnection()).thenThrow(new TimeoutException("Connection timeout")); - // Act - Mono result = indicator.doHealthCheck(new Builder()); - // Assert - StepVerifier.create(result) - .expectError(TimeoutException.class) - .verify(); - } -} diff --git a/async/async-rabbit-starter/src/test/resources/application.properties b/async/async-rabbit-starter/src/test/resources/application.properties deleted file mode 100644 index 3d8d7db0..00000000 --- a/async/async-rabbit-starter/src/test/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=test-app \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/1-getting-started.md b/docs/docs/reactive-commons-eda/1-getting-started.md deleted file mode 100644 index 55cc1eaf..00000000 --- a/docs/docs/reactive-commons-eda/1-getting-started.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Getting Started - -## What Changes in this Variant? - -Before start this guide please review base [Reactive Commons](/reactive-commons-java/docs/category/reactive-commons) - -### Multi Broker or Multi Domain support - -This variant enables to you the ability to listen events from different domains, send commands to different domains and make queries to different domains. - -### Cloud Events - -This variant also includes the Cloud Events specification - -## Setup - -### Current version -![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Freactivecommons%2Fasync-commons-rabbit-starter-eda%2Fmaven-metadata.xml) - -### Dependency - -```groovy -dependencies { - implementation "org.reactivecommons:async-commons-rabbit-starter-eda:" -} -``` - -### Configuration properties - -Also you need to include the name for your app in the `application.properties`, it is important because this value will be used -to name the application queues inside RabbitMQ: - -```properties -spring.application.name=MyAppName -``` - -Or in your `application.yaml` - -```yaml -spring: - application: - name: MyAppName -``` - -You can set the RabbitMQ connection properties through spring boot with the [`spring.rabbitmq.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) - -```yaml -spring: - rabbitmq: - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / -``` - -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" -@Configuration -public class MyRabbitMQConfig { - - @Bean - @Primary - public RabbitProperties customRabbitProperties(){ - RabbitProperties properties = new RabbitProperties(); - properties.setHost("localhost"); - properties.setPort(5672); - properties.setVirtualHost("/"); - properties.setUsername("guest"); - properties.setPassword("guest"); - return properties; - } -} -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/3-additional-api-methods.md b/docs/docs/reactive-commons-eda/3-additional-api-methods.md deleted file mode 100644 index 0a0e8d8a..00000000 --- a/docs/docs/reactive-commons-eda/3-additional-api-methods.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Additional API Definitions - -Returning to the base API, the additional methods enabled for EDA and CloudEvents are indicated below. - -## Aditional Methods in Senders - -### DomainEventBus interface - -This interface has one additional method specific for EDA - -```java -public interface DomainEventBus { - Publisher emit(CloudEvent event); // Emit with CloudEvent format - - //... other base definitions -} -``` - -### DirectAsyncGateway interface - -Ths interface adds some actions for commands and async queries - -```java -public interface DirectAsyncGateway { - Mono sendCommand(Command command, String targetName, String domain); // Send to specific domain - - Mono sendCommand(Command command, String targetName, long delayMillis, String domain); // Send to specific domain with delay - - Mono sendCommand(CloudEvent command, String targetName); // Send with CloudEvent format - - Mono sendCommand(CloudEvent command, String targetName, String domain); // Send with CloudEvent format to an specific domain - - Mono requestReply(AsyncQuery query, String targetName, Class type, String domain); // Query to specific domain - - Mono requestReply(CloudEvent query, String targetName, Class type); // Query with CloudEvent format - - Mono requestReply(CloudEvent query, String targetName, Class type, String domain); // Query with CloudEvent format to specific domain -} -``` - -## Aditional Listener capabilities - -On the other hand, for listener you can use additional capabilities like listen from another domain - -### HandlerRegistry class - -```java -public class HandlerRegistry { - public HandlerRegistry listenDomainEvent(String domain, String eventName, EventHandler handler, Class eventClass) {...} // Class could be CloudEvent.class - - // ... other base methods -} -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/4-configuration-properties.md b/docs/docs/reactive-commons-eda/4-configuration-properties.md deleted file mode 100644 index 6cfdcd0c..00000000 --- a/docs/docs/reactive-commons-eda/4-configuration-properties.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Configuration Properties - -You can customize some predefined variables of Reactive Commons - -This can be done by Spring Boot `application.yaml` or by overriding the [AsyncPropsDomainProperties](https://github.com/reactive-commons/reactive-commons-java/blob/master/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java) bean. - -```yaml -app: - async: - app: # this is the name of the default domain - withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. - maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. - retryDelay: 1000 # interval for message retries, with and without DLQRetry - listenReplies: true # if you will not use ReqReply patter you can set it to false - createTopology: true # if your organization have restricctions 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 - flux: - maxConcurrency: 250 # max concurrency of listener flow - domain: - ignoreThisListener: false # Allows you to disable event listener for this specific domain - events: - exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently - eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} - notificationSuffix: notification # notification events queue name suffix - direct: - exchange: directMessages # you can change the exchange, but you should do it in all applications - querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} - commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default - discardTimeoutQueries: false # enable to discard this condition - global: - exchange: globalReply # you can change the exchange, but you should do it in all applications - repliesSuffix: replies # async query replies events queue name suffix - connectionProperties: # you can override the connection properties of each domain - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / - accounts: # this is a second domain name and can have another independent settup - connectionProperties: # you can override the connection properties of each domain - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: /accounts -``` - -You can override this settings programatically through a `AsyncPropsDomainProperties` bean. - -```java -package sample; - -import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; - -@Configuration -public class MyDomainConfig { - - @Bean - @Primary - public AsyncPropsDomainProperties customDomainProperties() { - RabbitProperties propertiesApp = new RabbitProperties(); - propertiesApp.setHost("localhost"); - propertiesApp.setPort(5672); - propertiesApp.setVirtualHost("/"); - propertiesApp.setUsername("guest"); - propertiesApp.setPassword("guest"); - - RabbitProperties propertiesAccounts = new RabbitProperties(); - propertiesAccounts.setHost("localhost"); - propertiesAccounts.setPort(5672); - propertiesAccounts.setVirtualHost("/accounts"); - propertiesAccounts.setUsername("guest"); - propertiesAccounts.setPassword("guest"); - - return AsyncPropsDomainProperties.builder() - .withDomain("app", AsyncProps.builder() - .connectionProperties(propertiesApp) - .build()) - .withDomain("accounts", AsyncProps.builder() - .connectionProperties(propertiesAccounts) - .build()) - .build(); - } -} -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/_category_.json b/docs/docs/reactive-commons-eda/_category_.json deleted file mode 100644 index bf403fcf..00000000 --- a/docs/docs/reactive-commons-eda/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Reactive Commons EDA", - "position": 3, - "link": { - "type": "generated-index", - "description": "Learn how to build reactive systems using the Reactive Commons library." - } -} diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index f0d53b65..e8593755 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -2,9 +2,15 @@ sidebar_position: 1 --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + # Getting Started -This quick start tutorial sets up a single node RabbitMQ and runs the sample reactive sender and consumer using Reactive Commons. +This quick start tutorial sets up a single node RabbitMQ and runs the sample reactive sender and consumer using Reactive +Commons. ## Requirements @@ -16,7 +22,7 @@ You also need to install RabbitMQ. Follow the [instructions from the website](ht Start RabbitMQ on your local machine with all the defaults (e.g. AMQP port is 5672). -### Contenerized +### Containerized You can run it with Docker or Podman @@ -26,23 +32,27 @@ podman run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management-al ## Spring Boot Application -The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to configure Reactive Commons using RabbitMQ in a Spring Boot environment. +The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to +configure Reactive Commons using RabbitMQ in a Spring Boot environment. To build your own application using the Reactive Commons API, you need to include a dependency to Reactive Commons. ### Current version + ![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Freactivecommons%2Fasync-commons-rabbit-starter%2Fmaven-metadata.xml) ### Dependency ```groovy dependencies { - implementation "org.reactivecommons:async-commons-rabbit-starter:" + implementation "org.reactivecommons:async-commons-rabbit-starter:" } ``` + ### Configuration properties -Also you need to include the name for your app in the `application.properties`, it is important because this value will be used +Also you need to include the name for your app in the `application.properties`, it is important because this value will +be used to name the application queues inside RabbitMQ: ```properties @@ -57,27 +67,29 @@ spring: name: MyAppName ``` -You can set the RabbitMQ connection properties through spring boot with the [`spring.rabbitmq.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) +You can set the RabbitMQ connection properties through spring boot with +the [`spring.rabbitmq.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) ```yaml spring: rabbitmq: - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / ``` 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" + @Configuration public class MyRabbitMQConfig { @Bean @Primary - public RabbitProperties customRabbitProperties(){ + public RabbitProperties customRabbitProperties() { RabbitProperties properties = new RabbitProperties(); properties.setHost("localhost"); properties.setPort(5672); @@ -87,4 +99,27 @@ public class MyRabbitMQConfig { return properties; } } -``` \ No newline at end of file +``` + +The 5.x.x stable version of Reactive Commons has been merged with the deprecated `-eda` variant, this means that +the `async-commons-rabbit-starter` artifact is now the only one to use. + +This merge allows you to: + +### Multi Broker Instances of RabbitMQ or Multi Domain support + +Enables to you the ability to listen events from different domains, send commands to different domains and make queries +to different domains. + +### Cloud Events + +Includes the Cloud Events specification. + +If you want to use it, you should read the [Creating a CloudEvent guide](11-creating-a-cloud-event.md) + + + + Comming soon... + + + diff --git a/docs/docs/reactive-commons/10-wildcards.md b/docs/docs/reactive-commons/10-wildcards.md index 20270a1c..5ee01bbe 100644 --- a/docs/docs/reactive-commons/10-wildcards.md +++ b/docs/docs/reactive-commons/10-wildcards.md @@ -2,11 +2,19 @@ sidebar_position: 10 --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + # Wildcards -You may need to listen variable event names that have the same structure, in that case you have the method `handleDynamicEvents` in the `HandlerRegistry`, so you can specify a pattern with '*' wildcard, it does not creates a binding in the broker, but allows that you do it dynamically through a `DynamicRegistry` class. +You may need to listen variable event names that have the same structure, in that case you have the +method `handleDynamicEvents` in the `HandlerRegistry`, so you can specify a pattern with '*' wildcard, it does not +creates a binding in the broker, but allows that you do it dynamically through a `DynamicRegistry` class. -You can also create binding with '#' wildcard, it is used to listen multiple words, for example `animals.#` will listen to `animals.dog`, `animals.dog.bark`, `animals.cat`, `animals.cat.meow`, etc. +You can also create binding with '#' wildcard, it is used to listen multiple words, for example `animals.#` will listen +to `animals.dog`, `animals.dog.bark`, `animals.cat`, `animals.cat.meow`, etc. ## DynamicRegistry API @@ -22,16 +30,18 @@ public interface DynamicRegistry { } ``` -To start listening a new event dynamically at runtime, you should inject and call a method of the DynamicReggistry, for example: +To start listening a new event dynamically at runtime, you should inject and call a method of the DynamicReggistry, for +example: ```java + @Component @AllArgsConstructor public class DynamicSubscriber { private final DynamicRegistry registry; public Mono listenNewEvent(String eventName) { - return registry.startListeningEvent(eventName); + return registry.startListeningEvent(eventName); } } ``` @@ -41,6 +51,7 @@ The conditions for a success dynamic registry functionality are: - You should handle dynamic events with specific wildcard ```java + @Configuration public class HandlerRegistryConfiguration { @@ -58,12 +69,14 @@ public class HandlerRegistryConfiguration { registry.startListeningEvent("purchase.cancelled"); ``` -You also can listen with * wildcard or # wildcard, the * wildcard is for a single word and # wildcard is for multiple words, for example: +You also can listen with * wildcard or # wildcard, the * wildcard is for a single word and # wildcard is for multiple +words, for example: `animals.*` will listen to `animals.dog`, `animals.cat`, `animals.bird`, etc. `animals.#` will listen to `animals.dog`, `animals.dog.bark`, `animals.cat`, `animals.cat.meow`, etc. ```java + @Configuration public class HandlerRegistryConfiguration { @@ -76,37 +89,54 @@ public class HandlerRegistryConfiguration { } ``` -This last approach is useful when you have a dynamic event name, for example, you can have a `purchase.cancelled` event, but you can also have a `purchase.cancelled.2021` event, so you can listen to all of them with `purchase.*` or `purchase.#` respectively. +This last approach is useful when you have a dynamic event name, for example, you can have a `purchase.cancelled` event, +but you can also have a `purchase.cancelled.2021` event, so you can listen to all of them with `purchase.*` +or `purchase.#` respectively. ## Priorities -The handlers with wildcards have the lowest priority, so if you have a specific handler for an event name, it will be called before the wildcard handler. +The handlers with wildcards have the lowest priority, so if you have a specific handler for an event name, it will be +called before the wildcard handler. -The wildcard handler will be called if there is no specific handler for the event name. And the wildcard matches the pattern. +The wildcard handler will be called if there is no specific handler for the event name. And the wildcard matches the +pattern. General conditions for handler priority are: + - fixed words has priority over wildcard - wildcard with * has priority over wildcard with # - wildcard with # has the lowest priority The next code will help you to avoid unexpected behaviors, which indicates you the handler that will be called. + ```java public static void main(String[] args) { - Set names = Set.of("prefix.*.*", "prefix.*.#"); - String target = "prefix.middle.suffix"; - String handler = new KeyMatcher().match(names, target); - System.out.println(handler); - } + Set names = Set.of("prefix.*.*", "prefix.*.#"); + String target = "prefix.middle.suffix"; + String handler = new KeyMatcher().match(names, target); + System.out.println(handler); +} ``` ## Possible issues -Unprocessed events will be notified with an event, so please handle it properly and ensure you don't have wildcards that match all events, because it can cause unexpected behaviors. +Unprocessed events will be notified with an event, so please handle it properly and ensure you don't have wildcards that +match all events, because it can cause unexpected behaviors. -For example if you have a handler for `my.event.#` and you start listening to `my.event`, it will match all events that start with `my.event`, so it will match `my.event.any` if your handler has dlq retries or has local retries, when your handler fails the retry limit, an event with name `my.event.any.dlq` will be sent. +For example if you have a handler for `my.event.#` and you start listening to `my.event`, it will match all events that +start with `my.event`, so it will match `my.event.any` if your handler has dlq retries or has local retries, when your +handler fails the retry limit, an event with name `my.event.any.dlq` will be sent. -And if you have the same binding `my.event.#` the event will be handled by the handler, so you will have two different event structures for the same handler, and it can cause unexpected behaviors. +And if you have the same binding `my.event.#` the event will be handled by the handler, so you will have two different +event structures for the same handler, and it can cause unexpected behaviors. ## Example -You can see a real example at [samples/async/async-receiver-responder](https://github.com/reactive-commons/reactive-commons-java/tree/master/samples/async/async-receiver-responder) \ No newline at end of file +You can see a real example +at [samples/async/async-receiver-responder](https://github.com/reactive-commons/reactive-commons-java/tree/master/samples/async/async-receiver-responder) + + + + Comming soon... + + \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/2-creating-a-cloud-event.md b/docs/docs/reactive-commons/11-creating-a-cloud-event.md similarity index 58% rename from docs/docs/reactive-commons-eda/2-creating-a-cloud-event.md rename to docs/docs/reactive-commons/11-creating-a-cloud-event.md index a0bce0b6..1db4d460 100644 --- a/docs/docs/reactive-commons-eda/2-creating-a-cloud-event.md +++ b/docs/docs/reactive-commons/11-creating-a-cloud-event.md @@ -1,36 +1,31 @@ --- -sidebar_position: 2 +sidebar_position: 11 --- # Creating a CloudEvent -## Aditional Dependencies +## Additional Dependencies To start using this approach you should know the base of `Events`, `Commands` and `AsyncQuery` -This varian includes an object mapper that allows to you to emit CloudEvent serialize and deserialize. +This variant includes an object mapper that allows to you to emit CloudEvent serialize and deserialize. Each API includes overloads related to emit CloudEvent events, send CloudEvent commands and make CloudEvent async queries. -In order to instantiate a CloudEvent you need to include the dependencies: +In order to instantiate a CloudEvent you may need to include the dependencies: ```groovy -implementation 'io.cloudevents:cloudevents-core:3.0.0' -implementation 'io.cloudevents:cloudevents-http-basic:3.0.0' -implementation 'io.cloudevents:cloudevents-json-jackson:3.0.0' -implementation 'io.cloudevents:cloudevents-spring:3.0.0' +implementation 'io.cloudevents:cloudevents-core:' ``` ## Creating a CloudEvent instance ```java -ObjectMapper om = new ObjectMapper(); - CloudEvent commandCloudEvent = CloudEventBuilder.v1() .withId(UUID.randomUUID().toString()) .withSource(URI.create("https://reactivecommons.org/foos")) .withType("some.command.name") .withTime(OffsetDateTime.now()) - .withData("application/json", om.writeValueAsBytes(command)) // command is your own object + .withData("application/json", CloudEventBuilderExt.asCloudEventData(commandData)) // commandData is your own object .build(); CloudEvent queryCloudEvent = CloudEventBuilder.v1() @@ -38,7 +33,7 @@ CloudEvent queryCloudEvent = CloudEventBuilder.v1() .withSource(URI.create("https://reactivecommons.org/foos")) .withType("some.query.name") .withTime(OffsetDateTime.now()) - .withData("application/json", om.writeValueAsBytes(query)) // query is your own object + .withData("application/json", CloudEventBuilderExt.asCloudEventData(queryData)) // queryData is your own object .build(); CloudEvent eventCloudEvent = CloudEventBuilder.v1() @@ -47,6 +42,6 @@ CloudEvent eventCloudEvent = CloudEventBuilder.v1() .withType("some.event.name") .withDataContentType("application/json") .withTime(OffsetDateTime.now()) - .withData("application/json", om.writeValueAsBytes(event)) // event is your own object + .withData("application/json", CloudEventBuilderExt.asCloudEventData(eventData)) // eventData is your own object .build(); ``` \ No newline at end of file diff --git a/docs/docs/reactive-commons/2-sending-a-domain-event.md b/docs/docs/reactive-commons/2-sending-a-domain-event.md index b48fc7ac..243e1a72 100644 --- a/docs/docs/reactive-commons/2-sending-a-domain-event.md +++ b/docs/docs/reactive-commons/2-sending-a-domain-event.md @@ -26,7 +26,7 @@ Where name is the event name, eventId is an unique event identifier and data is public interface DomainEventBus { Publisher emit(DomainEvent event); - //... other definitions for eda variant + Publisher emit(CloudEvent event); } ``` diff --git a/docs/docs/reactive-commons/3-sending-a-command.md b/docs/docs/reactive-commons/3-sending-a-command.md index 692bd9cb..295f0b50 100644 --- a/docs/docs/reactive-commons/3-sending-a-command.md +++ b/docs/docs/reactive-commons/3-sending-a-command.md @@ -29,7 +29,13 @@ public interface DirectAsyncGateway { Mono sendCommand(Command command, String targetName, long delayMillis); - //... other definitions for queries and eda variant + Mono sendCommand(Command command, String targetName, String domain); // Send to specific domain + + Mono sendCommand(Command command, String targetName, long delayMillis, String domain); // Send to specific domain with delay + + Mono sendCommand(CloudEvent command, String targetName); // Send with CloudEvent format + + Mono sendCommand(CloudEvent command, String targetName, String domain); // Send with CloudEvent format to an specific domain } ``` diff --git a/docs/docs/reactive-commons/4-making-an-async-query.md b/docs/docs/reactive-commons/4-making-an-async-query.md index 00f7c6e0..21b24159 100644 --- a/docs/docs/reactive-commons/4-making-an-async-query.md +++ b/docs/docs/reactive-commons/4-making-an-async-query.md @@ -28,7 +28,11 @@ public interface DirectAsyncGateway { Mono requestReply(AsyncQuery query, String targetName, Class type); - //... other definitions for commands and eda variant + Mono requestReply(AsyncQuery query, String targetName, Class type, String domain); // Query to specific domain + + Mono requestReply(CloudEvent query, String targetName, Class type); // Query with CloudEvent format + + Mono requestReply(CloudEvent query, String targetName, Class type, String domain); // Query with CloudEvent format to specific domain } ``` diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index 1540b385..7cd967d4 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -2,35 +2,120 @@ sidebar_position: 8 --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + # Configuration Properties You can customize some predefined variables of Reactive Commons -This can be done by Spring Boot `application.yaml` or by overriding the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) bean. +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +bean. ```yaml app: async: - withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. - maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. - retryDelay: 1000 # interval for message retries, with and without DLQRetry - listenReplies: true # if you will not use ReqReply patter you can set it to false - createTopology: true # if your organization have restricctions 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 - flux: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + listenReplies: true # if you will not use ReqReply patter you can set it to false + createTopology: true # if your organization have restricctions 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 + flux: maxConcurrency: 250 # max concurrency of listener flow - domain: + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain events: - exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently - eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} - notificationSuffix: notification # notification events queue name suffix - direct: + exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently + eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} + notificationSuffix: notification # notification events queue name suffix + direct: exchange: directMessages # you can change the exchange, but you should do it in all applications querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default discardTimeoutQueries: false # enable to discard this condition - global: + global: exchange: globalReply # you can change the exchange, but you should do it in all applications repliesSuffix: replies # async query replies events queue name suffix -``` \ No newline at end of file + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent settup + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: /accounts +``` + +You can override this settings programmatically through a `AsyncPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncPropsDomainProperties customDomainProperties() { + RabbitProperties propertiesApp = new RabbitProperties(); + propertiesApp.setHost("localhost"); + propertiesApp.setPort(5672); + propertiesApp.setVirtualHost("/"); + propertiesApp.setUsername("guest"); + propertiesApp.setPassword("guest"); + + RabbitProperties propertiesAccounts = new RabbitProperties(); + propertiesAccounts.setHost("localhost"); + propertiesAccounts.setPort(5672); + propertiesAccounts.setVirtualHost("/accounts"); + propertiesAccounts.setUsername("guest"); + propertiesAccounts.setPassword("guest"); + + return AsyncPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.SecretFiller` class. + +```java + +@Bean +@Primary +public AsyncPropsDomain.SecretFiller customFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + + Comming soon... + + \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json index 86fe140b..56d21e31 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,17 +8,17 @@ "name": "docs", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/preset-classic": "3.2.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", + "@mdx-js/react": "^3.0.1", + "clsx": "^2.1.1", + "prism-react-renderer": "^2.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/types": "3.2.1" + "@docusaurus/module-type-aliases": "^3.5.2", + "@docusaurus/types": "^3.5.2" }, "engines": { "node": ">=18.0" @@ -66,74 +66,125 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.2.tgz", - "integrity": "sha512-PvRQdCmtiU22dw9ZcTJkrVKgNBVAxKgD0/cfiqyxhA5+PHzA2WDt6jOmZ9QASkeM2BpyzClJb/Wr1yt2/t78Kw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", "dependencies": { - "@algolia/cache-common": "4.23.2" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/cache-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.2.tgz", - "integrity": "sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==" }, "node_modules/@algolia/cache-in-memory": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.2.tgz", - "integrity": "sha512-rfbi/SnhEa3MmlqQvgYz/9NNJ156NkU6xFxjbxBtLWnHbpj+qnlMoKd+amoiacHRITpajg6zYbLM9dnaD3Bczw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", "dependencies": { - "@algolia/cache-common": "4.23.2" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/client-account": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.2.tgz", - "integrity": "sha512-VbrOCLIN/5I7iIdskSoSw3uOUPF516k4SjDD4Qz3BFwa3of7D9A0lzBMAvQEJJEPHWdVraBJlGgdJq/ttmquJQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.2.tgz", - "integrity": "sha512-lLj7irsAztGhMoEx/SwKd1cwLY6Daf1Q5f2AOsZacpppSvuFvuBrmkzT7pap1OD/OePjLKxicJS8wNA0+zKtuw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.2.tgz", - "integrity": "sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g==", + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", "dependencies": { - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.1.1.tgz", + "integrity": "sha512-jkQNQbGY+XQB3Eln7wqqdUZKBzG8lETcsaUk5gcMc6iIwyN/qW0v0fhpKPH+Kli+BImLxo0CWk12CvVvx2exWA==", + "peer": true, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.2.tgz", - "integrity": "sha512-vwPsgnCGhUcHhhQG5IM27z8q7dWrN9itjdvgA6uKf2e9r7vB+WXt4OocK0CeoYQt3OGEAExryzsB8DWqdMK5wg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-search": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.2.tgz", - "integrity": "sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.1.1.tgz", + "integrity": "sha512-SFpb3FI/VouGou/vpuS7qeCA5Y/KpV42P6CEA/1MZQtl/xJkl6PVjikb+Q9YadeHi2jtDV/aQ6PyiVDnX4PQcw==", + "peer": true, "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "5.1.1", + "@algolia/requester-browser-xhr": "5.1.1", + "@algolia/requester-node-http": "5.1.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/events": { @@ -142,65 +193,108 @@ "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" }, "node_modules/@algolia/logger-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.2.tgz", - "integrity": "sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==" }, "node_modules/@algolia/logger-console": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.2.tgz", - "integrity": "sha512-oo+lnxxEmlhTBTFZ3fGz1O8PJ+G+8FiAoMY2Qo3Q4w23xocQev6KqDTA1JQAGPDxAewNA2VBwWOsVXeXFjrI/Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", "dependencies": { - "@algolia/logger-common": "4.23.2" + "@algolia/logger-common": "4.24.0" } }, "node_modules/@algolia/recommend": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.2.tgz", - "integrity": "sha512-Q75CjnzRCDzgIlgWfPnkLtrfF4t82JCirhalXkSSwe/c1GH5pWh4xUyDOR3KTMo+YxxX3zTlrL/FjHmUJEWEcg==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.2", - "@algolia/cache-common": "4.23.2", - "@algolia/cache-in-memory": "4.23.2", - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/logger-console": "4.23.2", - "@algolia/requester-browser-xhr": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/requester-node-http": "4.23.2", - "@algolia/transporter": "4.23.2" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dependencies": { + "@algolia/requester-common": "4.24.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.2.tgz", - "integrity": "sha512-TO9wLlp8+rvW9LnIfyHsu8mNAMYrqNdQ0oLF6eTWFxXfxG3k8F/Bh7nFYGk2rFAYty4Fw4XUtrv/YjeNDtM5og==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.1.1.tgz", + "integrity": "sha512-NXmN1ujJCj5GlJQaMK6DbdiXdcf6nhRef/X40lu9TYi71q9xTo/5RPMI0K2iOp6g07S26BrXFOz6RSV3Ny4LLw==", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.23.2" + "@algolia/client-common": "5.1.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.2.tgz", - "integrity": "sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==" }, "node_modules/@algolia/requester-node-http": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.2.tgz", - "integrity": "sha512-SVzgkZM/malo+2SB0NWDXpnT7nO5IZwuDTaaH6SjLeOHcya1o56LSWXk+3F3rNLz2GVH+I/rpYKiqmHhSOjerw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.1.1.tgz", + "integrity": "sha512-xwrgnNTIzgxDEx6zuCKSKTPzQLA8fL/WZiVB6fRpIu5agLMjoAi0cWA5YSDbo+2FFxqVgLqKY/Jz6mKmWtY15Q==", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.23.2" + "@algolia/client-common": "5.1.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.2.tgz", - "integrity": "sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", "dependencies": { - "@algolia/cache-common": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/requester-common": "4.23.2" + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" } }, "node_modules/@ampproject/remapping": { @@ -483,9 +577,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "engines": { "node": ">=6.9.0" } @@ -1575,11 +1669,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz", - "integrity": "sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.1.tgz", + "integrity": "sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2096,18 +2190,18 @@ } }, "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", + "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==" }, "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", + "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", + "@docsearch/css": "3.6.1", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -2132,9 +2226,9 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.2.1.tgz", - "integrity": "sha512-ZeMAqNvy0eBv2dThEeMuNzzuu+4thqMQakhxsgT5s02A8LqRcdkg+rbcnuNqUIpekQ4GRx3+M5nj0ODJhBXo9w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", + "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", "dependencies": { "@babel/core": "^7.23.3", "@babel/generator": "^7.23.3", @@ -2146,14 +2240,12 @@ "@babel/runtime": "^7.22.6", "@babel/runtime-corejs3": "^7.22.6", "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", - "@svgr/webpack": "^6.5.1", + "@docusaurus/cssnano-preset": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "autoprefixer": "^10.4.14", "babel-loader": "^9.1.3", "babel-plugin-dynamic-import-node": "^2.3.3", @@ -2167,8 +2259,8 @@ "copy-webpack-plugin": "^11.0.0", "core-js": "^3.31.1", "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^4.2.2", - "cssnano": "^5.1.15", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", "del": "^6.1.1", "detect-port": "^1.5.1", "escape-html": "^1.0.3", @@ -2188,7 +2280,7 @@ "prompts": "^2.4.2", "react-dev-utils": "^12.0.1", "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", "react-loadable-ssr-addon-v5-slorber": "^1.0.1", "react-router": "^5.3.4", "react-router-config": "^5.1.1", @@ -2214,18 +2306,19 @@ "node": ">=18.0" }, "peerDependencies": { + "@mdx-js/react": "^3.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.2.1.tgz", - "integrity": "sha512-wTL9KuSSbMJjKrfu385HZEzAoamUsbKqwscAQByZw4k6Ja/RWpqgVvt/CbAC+aYEH6inLzOt+MjuRwMOrD3VBA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", + "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", "dependencies": { - "cssnano-preset-advanced": "^5.3.10", - "postcss": "^8.4.26", - "postcss-sort-media-queries": "^4.4.1", + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.4.38", + "postcss-sort-media-queries": "^5.2.0", "tslib": "^2.6.0" }, "engines": { @@ -2233,9 +2326,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.2.1.tgz", - "integrity": "sha512-0voOKJCn9RaM3np6soqEfo7SsVvf2C+CDTWhW+H/1AyBhybASpExtDEz+7ECck9TwPzFQ5tt+I3zVugUJbJWDg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", + "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" @@ -2245,13 +2338,13 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.2.1.tgz", - "integrity": "sha512-Fs8tXhXKZjNkdGaOy1xSLXSwfjCMT73J3Zfrju2U16uGedRFRjgK0ojpK5tiC7TnunsL3tOFgp1BSMBRflX9gw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", + "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", "dependencies": { - "@docusaurus/logger": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -2283,18 +2376,17 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.2.1.tgz", - "integrity": "sha512-FyViV5TqhL1vsM7eh29nJ5NtbRE6Ra6LP1PDcPvhwPSlA7eiWGRKAn3jWwMUcmjkos5SYY+sr0/feCdbM3eQHQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", + "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", "dependencies": { - "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/types": "3.2.1", + "@docusaurus/types": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", "@types/react-router-dom": "*", "react-helmet-async": "*", - "react-loadable": "npm:@docusaurus/react-loadable@5.5.2" + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, "peerDependencies": { "react": "*", @@ -2302,18 +2394,19 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.2.1.tgz", - "integrity": "sha512-lOx0JfhlGZoZu6pEJfeEpSISZR5dQbJGGvb42IP13G5YThNHhG9R9uoWuo4IOimPqBC7sHThdLA3VLevk61Fsw==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", - "cheerio": "^1.0.0-rc.12", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", + "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", + "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", @@ -2328,23 +2421,25 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.2.1.tgz", - "integrity": "sha512-GHe5b/lCskAR8QVbfWAfPAApvRZgqk7FN3sOHgjCtjzQACZxkHmq6QqyqZ8Jp45V7lVck4wt2Xw2IzBJ7Cz3bA==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", + "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2363,15 +2458,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.2.1.tgz", - "integrity": "sha512-TOqVfMVTAHqWNEGM94Drz+PUpHDbwFy6ucHFgyTx9zJY7wPNSG5EN+rd/mU7OvAi26qpOn2o9xTdUmb28QLjEQ==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", + "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2385,13 +2480,13 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.2.1.tgz", - "integrity": "sha512-AMKq8NuUKf2sRpN1m/sIbqbRbnmk+rSA+8mNU1LNxEl9BW9F/Gng8m9HKlzeyMPrf5XidzS1jqkuTLDJ6KIrFw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", + "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2405,13 +2500,13 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.2.1.tgz", - "integrity": "sha512-/rJ+9u+Px0eTCiF4TNcNtj3kHf8cp6K1HCwOTdbsSlz6Xn21syZYcy+f1VM9wF6HrvUkXUcbM5TDCvg2IRL6bQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", + "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2423,13 +2518,13 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.2.1.tgz", - "integrity": "sha512-XtuJnlMvYfppeVdUyKiDIJAa/gTJKCQU92z8CLZZ9ibJdgVjFOLS10s0hIC0eL5z0U2u2loJz2rZ63HOkNHbBA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", + "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2442,13 +2537,13 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.2.1.tgz", - "integrity": "sha512-wiS/kE0Ny5pnjTxVCs8ljRnkL1RVMj59t6jmSsgEX7piDOoaXSMIUaoIt9ogS/v132uO0xEsxHstkRUZHQyPcQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", + "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2460,16 +2555,16 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.2.1.tgz", - "integrity": "sha512-uWZ7AxzdeaQSTCwD2yZtOiEm9zyKU+wqCmi/Sf25kQQqqFSBZUStXfaQ8OHP9cecnw893ZpZ811rPhB/wfujJw==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", + "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2483,23 +2578,23 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.2.1.tgz", - "integrity": "sha512-E3OHSmttpEBcSMhfPBq3EJMBxZBM01W1rnaCUTXy9EHvkmB5AwgTfW1PwGAybPAX579ntE03R+2zmXdizWfKnQ==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/plugin-content-blog": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/plugin-content-pages": "3.2.1", - "@docusaurus/plugin-debug": "3.2.1", - "@docusaurus/plugin-google-analytics": "3.2.1", - "@docusaurus/plugin-google-gtag": "3.2.1", - "@docusaurus/plugin-google-tag-manager": "3.2.1", - "@docusaurus/plugin-sitemap": "3.2.1", - "@docusaurus/theme-classic": "3.2.1", - "@docusaurus/theme-common": "3.2.1", - "@docusaurus/theme-search-algolia": "3.2.1", - "@docusaurus/types": "3.2.1" + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", + "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/plugin-debug": "3.5.2", + "@docusaurus/plugin-google-analytics": "3.5.2", + "@docusaurus/plugin-google-gtag": "3.5.2", + "@docusaurus/plugin-google-tag-manager": "3.5.2", + "@docusaurus/plugin-sitemap": "3.5.2", + "@docusaurus/theme-classic": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-search-algolia": "3.5.2", + "@docusaurus/types": "3.5.2" }, "engines": { "node": ">=18.0" @@ -2509,39 +2604,27 @@ "react-dom": "^18.0.0" } }, - "node_modules/@docusaurus/react-loadable": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", - "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", - "dependencies": { - "@types/react": "*", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "*" - } - }, "node_modules/@docusaurus/theme-classic": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.2.1.tgz", - "integrity": "sha512-+vSbnQyoWjc6vRZi4vJO2dBU02wqzynsai15KK+FANZudrYaBHtkbLZAQhgmxzBGVpxzi87gRohlMm+5D8f4tA==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/plugin-content-blog": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/plugin-content-pages": "3.2.1", - "@docusaurus/theme-common": "3.2.1", - "@docusaurus/theme-translations": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", + "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.43", + "infima": "0.2.0-alpha.44", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2561,17 +2644,14 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.2.1.tgz", - "integrity": "sha512-d+adiD7L9xv6EvfaAwUqdKf4orsM3jqgeqAM+HAjgL/Ux0GkVVnfKr+tsoe+4ow4rHe6NUt+nkkW8/K8dKdilA==", - "dependencies": { - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/plugin-content-blog": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/plugin-content-pages": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", + "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", + "dependencies": { + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2585,23 +2665,24 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.2.1.tgz", - "integrity": "sha512-bzhCrpyXBXzeydNUH83II2akvFEGfhsNTPPWsk5N7e+odgQCQwoHhcF+2qILbQXjaoZ6B3c48hrvkyCpeyqGHw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", + "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", "dependencies": { "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/theme-common": "3.2.1", - "@docusaurus/theme-translations": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "algoliasearch": "^4.18.0", "algoliasearch-helper": "^3.13.3", "clsx": "^2.0.0", @@ -2620,9 +2701,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.2.1.tgz", - "integrity": "sha512-jAUMkIkFfY+OAhJhv6mV8zlwY6J4AQxJPTgLdR2l+Otof9+QdJjHNh/ifVEu9q0lp3oSPlJj9l05AaP7Ref+cg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", + "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -2632,9 +2713,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.2.1.tgz", - "integrity": "sha512-n/toxBzL2oxTtRTOFiGKsHypzn/Pm+sXyw+VSk1UbqbXQiHOwHwts55bpKwbcUgA530Is6kix3ELiFOv9GAMfw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", + "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", @@ -2652,13 +2733,13 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.2.1.tgz", - "integrity": "sha512-DPkIS/EPc+pGAV798PUXgNzJFM3HJouoQXgr0KDZuJVz1EkWbDLOcQwLIz8Qx7liI9ddfkN/TXTRQdsTPZNakw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", + "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", "dependencies": { - "@docusaurus/logger": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@svgr/webpack": "^6.5.1", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@svgr/webpack": "^8.1.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", @@ -2674,6 +2755,7 @@ "shelljs": "^0.8.5", "tslib": "^2.6.0", "url-loader": "^4.1.1", + "utility-types": "^3.10.0", "webpack": "^5.88.1" }, "engines": { @@ -2689,9 +2771,9 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.2.1.tgz", - "integrity": "sha512-N5vadULnRLiqX2QfTjVEU3u5vo6RG2EZTdyXvJdzDOdrLCGIZAfnf/VkssinFZ922sVfaFfQ4FnStdhn5TWdVg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", + "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", "dependencies": { "tslib": "^2.6.0" }, @@ -2708,15 +2790,17 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.2.1.tgz", - "integrity": "sha512-+x7IR9hNMXi62L1YAglwd0s95fR7+EtirjTxSN4kahYRWGqOi3jlQl1EV0az/yTEvKbxVvOPcdYicGu9dk4LJw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", + "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", "dependencies": { - "@docusaurus/logger": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", + "lodash": "^4.17.21", "tslib": "^2.6.0" }, "engines": { @@ -2989,11 +3073,11 @@ } }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", - "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3034,11 +3118,11 @@ } }, "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", - "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3049,11 +3133,11 @@ } }, "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", - "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3064,11 +3148,11 @@ } }, "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", - "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3079,11 +3163,11 @@ } }, "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", - "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3094,9 +3178,9 @@ } }, "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", - "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "engines": { "node": ">=12" }, @@ -3109,21 +3193,21 @@ } }, "node_modules/@svgr/babel-preset": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz", - "integrity": "sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^6.5.1", - "@svgr/babel-plugin-remove-jsx-attribute": "*", - "@svgr/babel-plugin-remove-jsx-empty-expression": "*", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^6.5.1", - "@svgr/babel-plugin-svg-dynamic-title": "^6.5.1", - "@svgr/babel-plugin-svg-em-dimensions": "^6.5.1", - "@svgr/babel-plugin-transform-react-native-svg": "^6.5.1", - "@svgr/babel-plugin-transform-svg-component": "^6.5.1" + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3134,18 +3218,18 @@ } }, "node_modules/@svgr/core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", - "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dependencies": { - "@babel/core": "^7.19.6", - "@svgr/babel-preset": "^6.5.1", - "@svgr/plugin-jsx": "^6.5.1", + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.1" + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3153,15 +3237,15 @@ } }, "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz", - "integrity": "sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "dependencies": { - "@babel/types": "^7.20.0", + "@babel/types": "^7.21.3", "entities": "^4.4.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3169,37 +3253,37 @@ } }, "node_modules/@svgr/plugin-jsx": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz", - "integrity": "sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "dependencies": { - "@babel/core": "^7.19.6", - "@svgr/babel-preset": "^6.5.1", - "@svgr/hast-util-to-babel-ast": "^6.5.1", + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", "url": "https://github.com/sponsors/gregberge" }, "peerDependencies": { - "@svgr/core": "^6.0.0" + "@svgr/core": "*" } }, "node_modules/@svgr/plugin-svgo": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz", - "integrity": "sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", "dependencies": { - "cosmiconfig": "^7.0.1", - "deepmerge": "^4.2.2", - "svgo": "^2.8.0" + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3210,21 +3294,21 @@ } }, "node_modules/@svgr/webpack": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-6.5.1.tgz", - "integrity": "sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", "dependencies": { - "@babel/core": "^7.19.6", - "@babel/plugin-transform-react-constant-elements": "^7.18.12", - "@babel/preset-env": "^7.19.4", + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@svgr/core": "^6.5.1", - "@svgr/plugin-jsx": "^6.5.1", - "@svgr/plugin-svgo": "^6.5.1" + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3421,9 +3505,9 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/mdast": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", - "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "dependencies": { "@types/unist": "*" } @@ -3571,9 +3655,9 @@ } }, "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@types/ws": { "version": "8.5.10", @@ -3584,9 +3668,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dependencies": { "@types/yargs-parser": "*" } @@ -3871,31 +3955,31 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.2.tgz", - "integrity": "sha512-8aCl055IsokLuPU8BzLjwzXjb7ty9TPcUFFOk0pYOwsE5DMVhE3kwCMFtsCFKcnoPZK7oObm+H5mbnSO/9ioxQ==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.2", - "@algolia/cache-common": "4.23.2", - "@algolia/cache-in-memory": "4.23.2", - "@algolia/client-account": "4.23.2", - "@algolia/client-analytics": "4.23.2", - "@algolia/client-common": "4.23.2", - "@algolia/client-personalization": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/logger-console": "4.23.2", - "@algolia/recommend": "4.23.2", - "@algolia/requester-browser-xhr": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/requester-node-http": "4.23.2", - "@algolia/transporter": "4.23.2" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.17.0.tgz", - "integrity": "sha512-R5422OiQjvjlK3VdpNQ/Qk7KsTIGeM5ACm8civGifOVWdRRV/3SgXuKmeNxe94Dz6fwj/IgpVmXbHutU4mHubg==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.4.tgz", + "integrity": "sha512-fvBCywguW9f+939S6awvRMstqMF1XXcd2qs1r1aGqL/PJ1go/DqN06tWmDVmhCDqBJanm++imletrQWf0G2S1g==", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -3903,6 +3987,41 @@ "algoliasearch": ">= 3.1 < 6" } }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -4014,9 +4133,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "funding": [ { "type": "opencollective", @@ -4032,11 +4151,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4244,20 +4363,20 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -4273,10 +4392,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -4392,9 +4511,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001608", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", - "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "funding": [ { "type": "opencollective", @@ -4654,9 +4773,9 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -4971,18 +5090,28 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cross-spawn": { @@ -5024,11 +5153,11 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "engines": { - "node": "^10 || ^12 || >=14" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { "postcss": "^8.0.9" @@ -5069,16 +5198,16 @@ } }, "node_modules/css-minimizer-webpack-plugin": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz", - "integrity": "sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", "dependencies": { - "cssnano": "^5.1.8", - "jest-worker": "^29.1.2", - "postcss": "^8.4.17", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" }, "engines": { "node": ">= 14.15.0" @@ -5111,14 +5240,6 @@ } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -5135,23 +5256,15 @@ } }, "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { @@ -5177,108 +5290,128 @@ } }, "node_modules/cssnano": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", - "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", "dependencies": { - "cssnano-preset-default": "^5.2.14", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/cssnano-preset-advanced": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz", - "integrity": "sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", "dependencies": { - "autoprefixer": "^10.4.12", - "cssnano-preset-default": "^5.2.14", - "postcss-discard-unused": "^5.1.0", - "postcss-merge-idents": "^5.1.1", - "postcss-reduce-idents": "^5.2.0", - "postcss-zindex": "^5.1.0" + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/cssnano-preset-default": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", - "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.1", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.4", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.2", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dependencies": { - "css-tree": "^1.1.2" + "css-tree": "~2.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5650,9 +5783,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.732", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.732.tgz", - "integrity": "sha512-nLrzr6UML+oqCyvseTxJ1WxLsnMSQPJCoHu+MuOuNiCp7BHEdG9AmCTw2Y9FU/wFFc/ETE0F6JVtzzPryEjecw==" + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -5673,9 +5806,9 @@ } }, "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -5881,12 +6014,11 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.1.tgz", - "integrity": "sha512-5mvUrF2suuv5f5cGDnDphIy4/gW86z82kl5qG6mM9z04SEQI4FB5Apmaw/TGEf3l55nLtMs5s51dmhUzvAHQCA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", + "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", "dependencies": { - "@types/estree": "^1.0.0", - "is-plain-obj": "^4.0.0" + "@types/estree": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/remcohaszing" @@ -6229,9 +6361,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6899,9 +7031,9 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz", - "integrity": "sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -7441,9 +7573,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.43", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", - "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "version": "0.2.0-alpha.44", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", + "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", "engines": { "node": ">=12" } @@ -7830,9 +7962,9 @@ } }, "node_modules/joi": { - "version": "17.12.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz", - "integrity": "sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", @@ -7961,11 +8093,14 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -8141,9 +8276,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", - "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -8225,9 +8360,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", @@ -8423,9 +8558,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", - "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -8474,9 +8609,9 @@ } }, "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -8558,9 +8693,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", - "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", "funding": [ { "type": "GitHub Sponsors", @@ -8644,9 +8779,9 @@ ] }, "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.1.tgz", + "integrity": "sha512-VGV2uxUzhEZmaP7NSFo2vtq7M2nUD+WfmYQD+d8i/1nHbzE+rMy9uzTvUybBbNiVbrhOZibg3gbyoARGqgDWyg==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -8783,9 +8918,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", @@ -8832,9 +8967,9 @@ ] }, "node_modules/micromark-extension-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", @@ -8904,9 +9039,9 @@ ] }, "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -8936,9 +9071,9 @@ ] }, "node_modules/micromark-extension-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", - "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -9017,9 +9152,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", - "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -10076,9 +10211,9 @@ ] }, "node_modules/micromark-util-subtokenize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", - "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10195,11 +10330,11 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -10385,9 +10520,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -10405,17 +10540,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -10681,9 +10805,9 @@ } }, "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, "node_modules/parse-json": { "version": "5.2.0", @@ -10808,9 +10932,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -10932,105 +11056,108 @@ } }, "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", "dependencies": { - "postcss-selector-parser": "^6.0.9", + "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0" }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, "peerDependencies": { "postcss": "^8.2.2" } }, "node_modules/postcss-colormin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", - "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-unused": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz", - "integrity": "sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-loader": { @@ -11054,136 +11181,111 @@ "webpack": "^5.0.0" } }, - "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/postcss-merge-idents": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz", - "integrity": "sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", "dependencies": { - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" + "stylehacks": "^6.1.1" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-merge-rules": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", - "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-modules-extract-imports": { @@ -11242,186 +11344,185 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "dependencies": { - "normalize-url": "^6.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", "dependencies": { - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-idents": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz", - "integrity": "sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-initial": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", - "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-selector-parser": { @@ -11437,46 +11538,46 @@ } }, "node_modules/postcss-sort-media-queries": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz", - "integrity": "sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", "dependencies": { - "sort-css-media-queries": "2.1.0" + "sort-css-media-queries": "2.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.0.0" }, "peerDependencies": { - "postcss": "^8.4.16" + "postcss": "^8.4.23" } }, "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" + "svgo": "^3.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >= 18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-value-parser": { @@ -11485,14 +11586,14 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/postcss-zindex": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.1.0.tgz", - "integrity": "sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/pretty-error": { @@ -11725,9 +11826,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -11854,15 +11955,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-error-overlay": { @@ -11897,9 +11998,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.3.0.tgz", - "integrity": "sha512-aN1biKC5v4DQkmQBlZjuMFR09MKZGMPtIg+cut8zEeg2HXd6gl2gRy0n4HMacHf0dznQgo0SVXN7eT8zV3hEuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", + "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", "engines": { "node": ">=14" }, @@ -11909,12 +12010,11 @@ }, "node_modules/react-loadable": { "name": "@docusaurus/react-loadable", - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", - "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "dependencies": { - "@types/react": "*", - "prop-types": "^15.6.2" + "@types/react": "*" }, "peerDependencies": { "react": "*" @@ -12464,9 +12564,9 @@ "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.2.0.tgz", + "integrity": "sha512-AV+V3oOVvCrqyH5Q/6RuT1IDH1Xy5kJTkEWTWZPN5rdQ3HCFOd8SrbC7c6N5Y8bPpCfZSR6yYbUATXslvfvu5g==", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -12527,14 +12627,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -12558,9 +12658,9 @@ } }, "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.16.3.tgz", + "integrity": "sha512-hSHy/s4Zk2xibhj9XTCACB+1PqS+CaJxepGNBhKc/OsHRpqvHAUAm5+uZ6kJJbGXn0pb3XqekHjg6JAqPExzqg==", "peer": true }, "node_modules/section-matter": { @@ -12918,9 +13018,9 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "node_modules/sitemap": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", - "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", + "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", @@ -12959,6 +13059,15 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -12970,9 +13079,9 @@ } }, "node_modules/sort-css-media-queries": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz", - "integrity": "sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", "engines": { "node": ">= 6.3.0" } @@ -13063,12 +13172,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -13204,18 +13307,18 @@ } }, "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/supports-color": { @@ -13246,23 +13349,27 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, "node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" }, "bin": { "svgo": "bin/svgo" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" } }, "node_modules/svgo/node_modules/commander": { @@ -13273,69 +13380,6 @@ "node": ">= 10" } }, - "node_modules/svgo/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/svgo/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -13662,9 +13706,9 @@ } }, "node_modules/unified": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", - "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -13798,9 +13842,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", @@ -13816,8 +13860,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -14050,9 +14094,9 @@ } }, "node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz", + "integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==", "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0", @@ -14064,9 +14108,9 @@ } }, "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" @@ -14306,9 +14350,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -14550,9 +14594,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, diff --git a/docs/package.json b/docs/package.json index fc9bb80f..eed0a7f1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,17 +14,17 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/preset-classic": "3.2.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", + "@mdx-js/react": "^3.0.1", + "clsx": "^2.1.1", + "prism-react-renderer": "^2.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/types": "3.2.1" + "@docusaurus/module-type-aliases": "^3.5.2", + "@docusaurus/types": "^3.5.2" }, "browserslist": { "production": [ diff --git a/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle b/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle index 538e3120..f8e8ef06 100644 --- a/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle +++ b/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle @@ -2,7 +2,7 @@ apply plugin: 'org.springframework.boot' dependencies { implementation project(":shared") - implementation project(":async-commons-rabbit-starter-eda") + implementation project(":async-commons-rabbit-starter") implementation 'org.springframework.boot:spring-boot-starter' implementation 'io.cloudevents:cloudevents-core:4.0.1' } \ No newline at end of file diff --git a/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle b/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle index 0c3c74d6..b4fa1123 100644 --- a/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle +++ b/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle @@ -2,7 +2,7 @@ apply plugin: 'org.springframework.boot' dependencies { implementation project(':shared') - implementation project(':async-commons-rabbit-starter-eda') + implementation project(':async-commons-rabbit-starter') implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' diff --git a/samples/async/eda-async-sender-client/eda-async-sender-client.gradle b/samples/async/eda-async-sender-client/eda-async-sender-client.gradle index 0c3c74d6..b4fa1123 100644 --- a/samples/async/eda-async-sender-client/eda-async-sender-client.gradle +++ b/samples/async/eda-async-sender-client/eda-async-sender-client.gradle @@ -2,7 +2,7 @@ apply plugin: 'org.springframework.boot' dependencies { implementation project(':shared') - implementation project(':async-commons-rabbit-starter-eda') + implementation project(':async-commons-rabbit-starter') implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' diff --git a/async/async-rabbit-standalone/async-commons-rabbit-standalone.gradle b/starters/async-rabbit-standalone/async-commons-rabbit-standalone.gradle similarity index 100% rename from async/async-rabbit-standalone/async-commons-rabbit-standalone.gradle rename to starters/async-rabbit-standalone/async-commons-rabbit-standalone.gradle diff --git a/async/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/config/DirectAsyncGatewayConfig.java similarity index 100% rename from async/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/config/DirectAsyncGatewayConfig.java diff --git a/async/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/config/EventBusConfig.java similarity index 100% rename from async/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/config/EventBusConfig.java diff --git a/async/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/config/RabbitMqConfig.java similarity index 100% rename from async/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/config/RabbitMqConfig.java diff --git a/async/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/config/RabbitProperties.java similarity index 100% rename from async/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/config/RabbitProperties.java diff --git a/async/async-rabbit-starter-eda/async-commons-rabbit-starter-eda.gradle b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle similarity index 100% rename from async/async-rabbit-starter-eda/async-commons-rabbit-starter-eda.gradle rename to starters/async-rabbit-starter/async-commons-rabbit-starter.gradle diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 96% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java index 1226625f..5df1231a 100644 --- a/async/async-rabbit-starter-eda/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 @@ -23,7 +23,6 @@ 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.DLQDiscardNotifier; import org.reactivecommons.async.rabbit.DynamicRegistryImp; import org.reactivecommons.async.rabbit.RabbitDomainEventBus; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; @@ -62,7 +61,7 @@ @Log @Configuration @RequiredArgsConstructor -@EnableConfigurationProperties({RabbitProperties.class, AsyncPropsDomainProperties.class}) +@EnableConfigurationProperties({RabbitPropertiesAutoConfig.class, AsyncPropsDomainProperties.class}) @Import({RabbitHealthConfig.class, AsyncPropsDomain.class}) public class RabbitMqConfig { @@ -240,8 +239,14 @@ public HandlerRegistry defaultHandlerRegistry() { @Bean @ConditionalOnMissingBean(AsyncPropsDomain.SecretFiller.class) public AsyncPropsDomain.SecretFiller defaultSecretFiller() { - return ignored -> { + 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 new file mode 100644 index 00000000..6c5991cb --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java @@ -0,0 +1,4 @@ +package org.reactivecommons.async.rabbit.config; + +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 new file mode 100644 index 00000000..4742df2f --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.rabbit.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.rabbitmq") +public class RabbitPropertiesAutoConfig extends RabbitPropertiesBase { +} diff --git a/async/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/RabbitPropertiesBase.java similarity index 98% rename from async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesBase.java index 1ed9934e..4849945b 100644 --- a/async/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/RabbitPropertiesBase.java @@ -1,6 +1,5 @@ package org.reactivecommons.async.rabbit.config; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DurationUnit; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -9,10 +8,8 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -@ConfigurationProperties(prefix = "spring.rabbitmq") -public class RabbitProperties { +public class RabbitPropertiesBase { /** * RabbitMQ host. @@ -92,6 +89,7 @@ public String getHost() { /** * Returns the host from the first address, or the configured host if no addresses * have been set. + * * @return the host * @see #setAddresses(String) * @see #getHost() @@ -114,6 +112,7 @@ public int getPort() { /** * Returns the port from the first address, or the configured port if no addresses * have been set. + * * @return the port * @see #setAddresses(String) * @see #getPort() @@ -137,6 +136,7 @@ public String getAddresses() { /** * Returns the comma-separated addresses or a single address ({@code host:port}) * created from the configured host and port if no addresses have been set. + * * @return the addresses */ public String determineAddresses() { @@ -170,6 +170,7 @@ public String getUsername() { /** * If addresses have been set and the first address has a username it is returned. * Otherwise returns the result of calling {@code getUsername()}. + * * @return the username * @see #setAddresses(String) * @see #getUsername() @@ -193,6 +194,7 @@ public String getPassword() { /** * If addresses have been set and the first address has a password it is returned. * Otherwise returns the result of calling {@code getPassword()}. + * * @return the password or {@code null} * @see #setAddresses(String) * @see #getPassword() @@ -220,6 +222,7 @@ public String getVirtualHost() { /** * If addresses have been set and the first address has a virtual host it is returned. * Otherwise returns the result of calling {@code getVirtualHost()}. + * * @return the virtual host or {@code null} * @see #setAddresses(String) * @see #getVirtualHost() @@ -416,15 +419,15 @@ public void setVerifyHostname(boolean verifyHostname) { public static class Cache { - private final Cache.Channel channel = new Cache.Channel(); + private final Channel channel = new Channel(); - private final Cache.Connection connection = new Cache.Connection(); + private final Connection connection = new Connection(); - public Cache.Channel getChannel() { + public Channel getChannel() { return this.channel; } - public Cache.Connection getConnection() { + public Connection getConnection() { return this.connection; } @@ -955,8 +958,7 @@ private void parseHostAndPort(String input) { if (portIndex == -1) { this.host = input; this.port = DEFAULT_PORT; - } - else { + } else { this.host = input.substring(0, portIndex); this.port = Integer.valueOf(input.substring(portIndex + 1)); } diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 90% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java index c31d52da..7da96bcf 100644 --- a/async/async-rabbit-starter-eda/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 @@ -7,7 +7,6 @@ import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.exceptions.InvalidConfigurationException; import org.springframework.beans.factory.annotation.Value; -import org.springframework.util.StringUtils; import java.util.HashMap; @@ -21,9 +20,13 @@ public AsyncPropsDomain(@Value("${spring.application.name}") String defaultAppNa AsyncPropsDomainProperties configured, SecretFiller secretFiller) { super(configured); - this.computeIfAbsent(DEFAULT_DOMAIN, k -> new AsyncProps()); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); + this.computeIfAbsent(DEFAULT_DOMAIN, k -> { + AsyncProps defaultApp = new AsyncProps(); + defaultApp.setConnectionProperties(mapper.convertValue(defaultRabbitProperties, RabbitProperties.class)); + return defaultApp; + }); super.forEach((key, value) -> { // To ensure that each domain has an appName if (value.getAppName() == null) { if (defaultAppName == null || defaultAppName.isEmpty()) { @@ -44,8 +47,8 @@ public AsyncPropsDomain(@Value("${spring.application.name}") String defaultAppNa if (value.getBrokerConfigProps() == null) { value.setBrokerConfigProps(new BrokerConfigProps(value)); } - if (StringUtils.hasText(value.getSecret()) && secretFiller != null) { - secretFiller.fillWithSecret(value); + if (secretFiller != null) { + secretFiller.fillWithSecret(key, value); } }); } @@ -102,7 +105,7 @@ public AsyncPropsDomain build() { } public interface SecretFiller { - void fillWithSecret(AsyncProps props); + void fillWithSecret(String domain, AsyncProps props); } } diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/Status.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/Status.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java diff --git a/async/async-rabbit-starter-eda/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 similarity index 100% rename from async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java similarity index 100% rename from async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java diff --git a/async/async-rabbit-starter-eda/src/test/resources/application.properties b/starters/async-rabbit-starter/src/test/resources/application.properties similarity index 100% rename from async/async-rabbit-starter-eda/src/test/resources/application.properties rename to starters/async-rabbit-starter/src/test/resources/application.properties From a75bb83c3b6b1b4d9634733991210e3c2c4a4bce Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 23 Aug 2024 13:14:30 +0000 Subject: [PATCH 10/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e597afae..394ddb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.0.2-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.2-alpha) (2024-08-23) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.1-alpha...v5.0.2-alpha) + +**Merged pull requests:** + +- chore\(next\): Unify rabbitmq starter with starter eda [\#117](https://github.com/reactive-commons/reactive-commons-java/pull/117) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.0.1-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.1-alpha) (2024-08-20) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.0-alpha...v5.0.1-alpha) diff --git a/gradle.properties b/gradle.properties index 45ae916b..5805b94c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.1-alpha +version=5.0.2-alpha toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter onlyUpdater=true \ No newline at end of file From 1887e898ec597e51d47eaad99912b6397ab71cb8 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:28:38 -0500 Subject: [PATCH 11/50] chore(next): Fix test, artifactid and docs (#118) --- .../json/KafkaJacksonMessageConverterTest.java | 18 +++++++++++++++--- docs/docusaurus.config.js | 4 ---- .../async-commons-rabbit-starter.gradle | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java index cdec5482..a07539fa 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.jackson.JsonCloudEventData; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -18,7 +17,7 @@ import java.net.URI; import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; +import java.util.Date; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -69,13 +68,15 @@ void shouldSerializeCloudEvent() throws JsonProcessingException { "\",\"source\":\"https://reactivecommons.org/events\",\"type\":\"test\"," + "\"datacontenttype\":\"application/json\",\"time\":\"" + dateTime + "\",\"data\":{\"name\":\"name\",\"age\":1}}"; + JsonCloudEvent expectedJsonNode = objectMapper.readValue(expectedJson, JsonCloudEvent.class); // Act Message message = converter.toMessage(testCloudEvent); // Assert assertEquals("test", message.getProperties().getTopic()); assertEquals(id, message.getProperties().getKey()); assertEquals("application/cloudevents+json", message.getProperties().getContentType()); - assertEquals(expectedJson, new String(message.getBody())); + JsonCloudEvent receivedJsonNode = objectMapper.readValue(new String(message.getBody()), JsonCloudEvent.class); + assertEquals(expectedJsonNode, receivedJsonNode); } @Data @@ -85,4 +86,15 @@ public static class MyEvent { private String name; private int age; } + + @Data + public static class JsonCloudEvent { + private String specversion; + private String id; + private String source; + private String type; + private String datacontenttype; + private Date time; + private MyEvent data; + } } diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 3f5a41a3..76ce0a31 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -97,10 +97,6 @@ const config = { label: 'Reactive Commons', to: '/docs/reactive-commons/getting-started', }, - { - label: 'Reactive Commons EDA', - to: '/docs/reactive-commons-eda/getting-started', - }, ], }, { diff --git a/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle index 7daa1162..395917e7 100644 --- a/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle +++ b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle @@ -1,6 +1,6 @@ ext { - artifactId = 'async-commons-rabbit-starter-eda' - artifactDescription = 'Async Commons Starter EDA' + artifactId = 'async-commons-rabbit-starter' + artifactDescription = 'Async Commons Starter' } dependencies { From 7c202c5fff9dcfeb32df65d172a3ed4eccd5f173 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 23 Aug 2024 13:39:38 +0000 Subject: [PATCH 12/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394ddb7e..d1d0ef2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.0.3-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.3-alpha) (2024-08-23) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.2-alpha...v5.0.3-alpha) + +**Merged pull requests:** + +- chore\(next\): Fix test, artifactid and docs [\#118](https://github.com/reactive-commons/reactive-commons-java/pull/118) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.0.2-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.2-alpha) (2024-08-23) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.1-alpha...v5.0.2-alpha) diff --git a/gradle.properties b/gradle.properties index 5805b94c..f503ff10 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.2-alpha +version=5.0.3-alpha toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter onlyUpdater=true \ No newline at end of file From 5e093a587d7814a45cc88eae16800cf8e63dbc64 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:00:10 -0500 Subject: [PATCH 13/50] chore(next): Update starters and extract common reusable code to an aditional module (#119) * chore(next): Update starters and extract common reusable code to an additional module --- async/async-commons/async-commons.gradle | 1 + .../commons}/HandlerResolverBuilder.java | 12 +- .../json/DefaultObjectMapperSupplier.java | 4 +- .../ReactiveMessageListener.java | 11 +- .../topology/TopologyCreator.java | 9 +- .../ApplicationNotificationsListener.java | 2 +- .../topology/TopologyCreatorTest.java | 6 +- .../listeners/ApplicationQueryListener.java | 3 - .../ApplicationCommandListenerTest.java | 4 +- .../reactive-commons/1-getting-started.md | 144 +- docs/docs/reactive-commons/10-wildcards.md | 3 +- .../9-configuration-properties.md | 87 +- gradle.properties | 2 +- .../src/main/java/sample/KafkaConfig.java | 8 +- .../src/main/java/sample/MyDomainConfig.java | 6 +- .../async-kafka-starter.gradle | 1 + .../async/kafka/config/ConnectionManager.java | 78 + .../async/kafka/config/DomainHandlers.java | 23 + .../async/kafka/config/KafkaProperties.java | 8 + .../config/KafkaPropertiesAutoConfig.java | 12 + .../async/kafka/config/RCKafkaConfig.java | 141 +- .../config/RCKafkaEventListenerConfig.java | 79 +- ...CKafkaNotificationEventListenerConfig.java | 50 +- .../kafka/config/props/AsyncKafkaProps.java | 46 + .../config/props/AsyncKafkaPropsDomain.java | 35 + .../AsyncKafkaPropsDomainProperties.java | 23 + .../async/kafka/config/props/DomainProps.java | 21 + .../kafka/config/props/RCAsyncPropsKafka.java | 28 - .../kafka/config/props/RCKafkaProps.java | 6 - .../config/spring/KafkaPropertiesBase.java | 1711 +++++++++++++++++ .../async-commons-rabbit-starter.gradle | 1 + .../rabbit/config/EventListenersConfig.java | 3 +- .../async/rabbit/config/RabbitMqConfig.java | 9 +- .../async/rabbit/config/props/AsyncProps.java | 9 +- .../rabbit/config/props/AsyncPropsDomain.java | 108 +- .../props/AsyncPropsDomainProperties.java | 38 - .../AsyncRabbitPropsDomainProperties.java | 24 + .../config/CommandListenersConfigTest.java | 6 +- .../config/EventListenersConfigTest.java | 4 +- .../NotificationListenersConfigTest.java | 4 +- .../config/QueryListenerConfigTest.java | 4 +- starters/shared/shared-starter.gradle | 10 + .../async/starter/GenericAsyncProps.java | 21 + .../starter/GenericAsyncPropsDomain.java | 156 ++ .../GenericAsyncPropsDomainProperties.java | 45 + .../InvalidConfigurationException.java | 2 +- 46 files changed, 2664 insertions(+), 344 deletions(-) rename {starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config => async/async-commons/src/main/java/org/reactivecommons/async/commons}/HandlerResolverBuilder.java (92%) create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/ConnectionManager.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/DomainHandlers.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java create mode 100644 starters/shared/shared-starter.gradle create mode 100644 starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java create mode 100644 starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java create mode 100644 starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java rename starters/{async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config => shared/src/main/java/org/reactivecommons/async/starter}/exceptions/InvalidConfigurationException.java (72%) diff --git a/async/async-commons/async-commons.gradle b/async/async-commons/async-commons.gradle index d4380d7d..12176cf5 100644 --- a/async/async-commons/async-commons.gradle +++ b/async/async-commons/async-commons.gradle @@ -9,6 +9,7 @@ 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 'io.cloudevents:cloudevents-json-jackson:4.0.1' diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java similarity index 92% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java index cff55f7b..fb903322 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java @@ -1,24 +1,24 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.commons; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import lombok.extern.log4j.Log4j2; +import lombok.extern.java.Log; import org.reactivecommons.async.api.DefaultCommandHandler; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; -import org.reactivecommons.async.commons.HandlerResolver; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; import java.util.stream.Stream; import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @NoArgsConstructor(access = AccessLevel.PRIVATE) -@Log4j2 +@Log public class HandlerResolverBuilder { public static HandlerResolver buildResolver(String domain, @@ -81,7 +81,7 @@ public RegisteredCommandHandler getCommandHandler(String path) { if (r.getDomainEventListeners().containsKey(domain)) { return Stream.concat(r.getDomainEventListeners().get(domain).stream(), getDynamics(domain, r)); } - log.warn("Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); + log.log(Level.WARNING, "Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); return Stream.empty(); }) .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), @@ -102,7 +102,7 @@ public RegisteredCommandHandler getCommandHandler(String path) { if (r.getDomainEventListeners().containsKey(domain)) { return r.getDomainEventListeners().get(domain).stream(); } - log.warn("Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); + log.log(Level.WARNING, "Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); return Stream.empty(); }) .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java index ac6fc677..f4a2faed 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import io.cloudevents.jackson.JsonFormat; public class DefaultObjectMapperSupplier implements ObjectMapperSupplier { @@ -11,7 +12,8 @@ public ObjectMapper get() { final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.findAndRegisterModules(); - objectMapper.registerModule(JsonFormat.getCloudEventJacksonModule()); // TODO: Review if this is necessary + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.registerModule(JsonFormat.getCloudEventJacksonModule()); return objectMapper; } diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java index 2a02808d..9cdf1921 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java @@ -1,7 +1,6 @@ package org.reactivecommons.async.kafka.communications; import lombok.AllArgsConstructor; -import org.apache.kafka.clients.consumer.ConsumerConfig; import reactor.core.publisher.Flux; import reactor.kafka.receiver.KafkaReceiver; import reactor.kafka.receiver.ReceiverOptions; @@ -9,22 +8,26 @@ import java.util.List; +import static org.apache.kafka.clients.consumer.ConsumerConfig.DEFAULT_MAX_POLL_RECORDS; +import static org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG; +import static org.apache.kafka.clients.consumer.ConsumerConfig.MAX_POLL_RECORDS_CONFIG; + @AllArgsConstructor public class ReactiveMessageListener { private final ReceiverOptions receiverOptions; public Flux> listen(String groupId, List topics) { // Notification events - ReceiverOptions options = receiverOptions.consumerProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId); + ReceiverOptions options = receiverOptions.consumerProperty(GROUP_ID_CONFIG, groupId); return KafkaReceiver.create(options.subscription(topics)) .receive(); } public int getMaxConcurrency() { - Object property = receiverOptions.consumerProperty(ConsumerConfig.MAX_POLL_RECORDS_CONFIG); + Object property = receiverOptions.consumerProperty(MAX_POLL_RECORDS_CONFIG); if (property instanceof Integer) { return (int) property; } - return ConsumerConfig.DEFAULT_MAX_POLL_RECORDS; + return DEFAULT_MAX_POLL_RECORDS; } } diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java index 9c1385cc..f7217197 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java @@ -19,15 +19,20 @@ public class TopologyCreator { private final AdminClient adminClient; private final KafkaCustomizations customizations; private final Map existingTopics; + private final boolean checkTopics; - public TopologyCreator(AdminClient adminClient, KafkaCustomizations customizations) { + public TopologyCreator(AdminClient adminClient, KafkaCustomizations customizations, boolean checkTopics) { this.adminClient = adminClient; this.customizations = customizations; + this.checkTopics = checkTopics; this.existingTopics = getTopics(); } @SneakyThrows public Map getTopics() { + if (!checkTopics) { + return Map.of(); + } ListTopicsResult topics = adminClient.listTopics(new ListTopicsOptions().timeoutMs(TIMEOUT_MS)); return topics.names().get().stream().collect(Collectors.toConcurrentMap(name -> name, name -> true)); } @@ -68,7 +73,7 @@ protected NewTopic toNewTopic(TopicCustomization customization) { } public void checkTopic(String topicName) { - if (!existingTopics.containsKey(topicName)) { + if (checkTopics && !existingTopics.containsKey(topicName)) { throw new TopicNotFoundException("Topic not found: " + topicName + ". Please create it before send a message."); // TODO: should refresh topics?? getTopics(); } diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java index bf4305e5..9758462f 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java @@ -44,7 +44,7 @@ public ApplicationNotificationsListener(ReactiveMessageListener receiver, @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredEventListener handler = resolver.getEventListener(executorPath); + final RegisteredEventListener handler = resolver.getNotificationListener(executorPath); Function converter = resolveConverter(handler); final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java index 87c15d24..63da00f3 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java @@ -58,7 +58,7 @@ void shouldCreateTopics() { create.complete(null); doReturn(create).when(createTopicsResult).all(); when(adminClient.createTopics(any())).thenReturn(createTopicsResult); - creator = new TopologyCreator(adminClient, customizations); + creator = new TopologyCreator(adminClient, customizations, true); // Act Mono flow = creator.createTopics(List.of("topic1", "topic2")); // Assert @@ -73,7 +73,7 @@ void shouldCheckTopics() { names.complete(Set.of("topic1", "topic2")); doReturn(names).when(listTopicsResult).names(); when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); - creator = new TopologyCreator(adminClient, customizations); + creator = new TopologyCreator(adminClient, customizations, true); // Act creator.checkTopic("topic1"); // Assert @@ -87,7 +87,7 @@ void shouldFailWhenCheckTopics() { names.complete(Set.of("topic1", "topic2")); doReturn(names).when(listTopicsResult).names(); when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); - creator = new TopologyCreator(adminClient, customizations); + creator = new TopologyCreator(adminClient, customizations, true); // Assert assertThrows(TopicNotFoundException.class, () -> // Act diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java index 59b40f6a..90c80154 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java @@ -2,8 +2,6 @@ import com.rabbitmq.client.AMQP; import io.cloudevents.CloudEvent; -import io.cloudevents.core.provider.EventFormatProvider; -import io.cloudevents.jackson.JsonFormat; import lombok.extern.java.Log; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; @@ -34,7 +32,6 @@ import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; @Log -//TODO: Organizar inferencia de tipos de la misma forma que en comandos y eventos public class ApplicationQueryListener extends GenericMessageListener { private final MessageConverter converter; private final HandlerResolver handlerResolver; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java index 4b74e5a6..e5f63737 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java @@ -14,7 +14,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.verify; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.error; @ExtendWith(MockitoExtension.class) public class ApplicationCommandListenerTest extends ListenerReporterTestSuperClass{ @@ -34,7 +34,7 @@ void shouldSendErrorMetricToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); assertSendErrorToCustomReporter(registry, createSource(Command::getName, command)); - verify(errorReporter).reportMetric(eq("command"), eq("app.command.test"), longThat(time -> time > 0 ), eq(false)); + verify(errorReporter).reportMetric(eq("command"), eq("app.command.test"), longThat(time -> time >= 0 ), eq(false)); } @Test diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index e8593755..5166d2f1 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -2,12 +2,13 @@ sidebar_position: 1 --- +# Getting Started + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Getting Started This quick start tutorial sets up a single node RabbitMQ and runs the sample reactive sender and consumer using Reactive Commons. @@ -119,7 +120,146 @@ If you want to use it, you should read the [Creating a CloudEvent guide](11-crea - Comming soon... + This quick start tutorial sets up a single node Kafka and runs the sample reactive sender and consumer using Reactive +Commons. + +## Requirements + +You need Java JRE installed (Java 17 or later). + +## Start Kafka + +Start a Kafka broker on your local machine with all the defaults (e.g. port is 9092). + +### Containerized + +You can run it with Docker or Podman. + +The following docker compose has a Kafka broker, a Zookeeper and a Kafka UI. + +docker-compose.yml +```yaml +services: + zookeeper: + image: confluentinc/cp-zookeeper:7.4.1 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ports: + - "2181:2181" + + kafka: + image: confluentinc/cp-kafka:7.4.1 + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + ports: + - "9092:9092" + depends_on: + - zookeeper + + kafka-ui: + image: provectuslabs/kafka-ui:latest + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 + KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 + ports: + - "8081:8080" + depends_on: + - kafka +``` + +```shell +docker-compose up +``` + +You may set in /etc/hosts (or equivalent) the following entry: + +```txt +127.0.0.1 kafka +``` + +To enter the Kafka UI, open your browser and go to `http://localhost:8081` + +## Spring Boot Application + +The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to +configure Reactive Commons using RabbitMQ in a Spring Boot environment. + +To build your own application using the Reactive Commons API, you need to include a dependency to Reactive Commons. + +### Current version + +![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Freactivecommons%2Fasync-commons-rabbit-starter%2Fmaven-metadata.xml) + +### Dependency + +```groovy +dependencies { + implementation "org.reactivecommons:async-kafka-starter:" +} +``` + +### Configuration properties + +Also you need to include the name for your app in the `application.properties`, it is important because this value will +be used +to name the application queues inside RabbitMQ: + +```properties +spring.application.name=MyAppName +``` + +Or in your `application.yaml` + +```yaml +spring: + application: + name: MyAppName +``` + +You can set the RabbitMQ connection properties through spring boot with +the [`spring.kafka.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) + +```yaml +spring: + kafka: + bootstrap-servers: localhost:9092 +``` + +You can also set it in runtime for example from a secret, so you can create the `KafkaProperties` bean like: + +```java title="org.reactivecommons.async.rabbit.config.RabbitProperties" + +@Configuration +public class MyKafkaConfig { + + @Bean + @Primary + public KafkaProperties myRCKafkaProperties() { + KafkaProperties properties = new KafkaProperties(); + properties.setBootstrapServers(List.of("localhost:9092")); + return properties; + } +} +``` + +### Multi Broker Instances of Kafka or Multi Domain support + +Enables to you the ability to listen events from different domains. + +### Cloud Events + +Includes the Cloud Events specification. + +If you want to use it, you should read the [Creating a CloudEvent guide](11-creating-a-cloud-event.md) + diff --git a/docs/docs/reactive-commons/10-wildcards.md b/docs/docs/reactive-commons/10-wildcards.md index 5ee01bbe..89b1c8dd 100644 --- a/docs/docs/reactive-commons/10-wildcards.md +++ b/docs/docs/reactive-commons/10-wildcards.md @@ -2,12 +2,13 @@ sidebar_position: 10 --- +# Wildcards + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Wildcards You may need to listen variable event names that have the same structure, in that case you have the method `handleDynamicEvents` in the `HandlerRegistry`, so you can specify a pattern with '*' wildcard, it does not diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index 7cd967d4..bfad7271 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -2,17 +2,18 @@ sidebar_position: 8 --- +# Configuration Properties + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Configuration Properties You can customize some predefined variables of Reactive Commons This can be done by Spring Boot `application.yaml` or by overriding -the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) bean. ```yaml @@ -23,7 +24,7 @@ app: maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. retryDelay: 1000 # interval for message retries, with and without DLQRetry listenReplies: true # if you will not use ReqReply patter you can set it to false - createTopology: true # if your organization have restricctions with automatic topology creation you can set it to false and create it manually or by your organization process. + 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 flux: @@ -49,7 +50,7 @@ app: password: guest virtual-host: / # Another domain can be configured with same properties structure that app - accounts: # this is a second domain name and can have another independent settup + accounts: # this is a second domain name and can have another independent setup connectionProperties: # you can override the connection properties of each domain host: localhost port: 5672 @@ -65,7 +66,7 @@ package sample; import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -116,6 +117,80 @@ public AsyncPropsDomain.SecretFiller customFiller() { - Comming soon... + You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncKafkaProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java) +bean. + +```yaml +reactive: + commons: + kafka: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + 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. + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9092 + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9093 +``` + +You can override this settings programmatically through a `AsyncKafkaPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncKafkaPropsDomainProperties customKafkaDomainProperties() { + KafkaProperties propertiesApp = new KafkaProperties(); + propertiesApp.setBootstrapServers(List.of("localhost:9092")); + + RabbitProperties propertiesAccounts = new RabbitProperties(); + propertiesAccounts.setBootstrapServers(List.of("localhost:9093")); + + return AsyncKafkaPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncKafkaPropsDomain.KafkaSecretFiller` +class. + +```java + +@Bean +@Primary +public AsyncKafkaPropsDomain.KafkaSecretFiller customKafkaFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f503ff10..23b4da41 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ version=5.0.3-alpha -toPublish=domain-events-api,async-commons-api,async-commons,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter +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 onlyUpdater=true \ No newline at end of file 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 525236ea..85e0ad3a 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,7 +1,7 @@ package sample; import org.reactivecommons.async.kafka.config.RCKafkaConfig; -import org.reactivecommons.async.kafka.config.props.RCAsyncPropsKafka; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -14,13 +14,13 @@ public class KafkaConfig { @Bean @Primary - public RCAsyncPropsKafka kafkaProps() throws IOException { - RCAsyncPropsKafka kafkaProps = new RCAsyncPropsKafka(); + public AsyncKafkaProps kafkaProps() throws IOException { + AsyncKafkaProps kafkaProps = new AsyncKafkaProps(); kafkaProps.setCreateTopology(true); kafkaProps.setMaxRetries(5); kafkaProps.setRetryDelay(1000); kafkaProps.setWithDLQRetry(true); - kafkaProps.setKafkaProps(RCKafkaConfig.readPropsFromDotEnv(Path.of(".kafka-env"))); + kafkaProps.setConnectionProperties(RCKafkaConfig.readPropsFromDotEnv(Path.of(".kafka-env"))); return kafkaProps; } } diff --git a/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java b/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java index 8b95a111..2d3b8924 100644 --- a/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java +++ b/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java @@ -2,7 +2,7 @@ import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -11,7 +11,7 @@ public class MyDomainConfig { @Bean @Primary - public AsyncPropsDomainProperties customDomainProperties() { + public AsyncRabbitPropsDomainProperties customDomainProperties() { RabbitProperties propertiesApp = new RabbitProperties(); propertiesApp.setHost("localhost"); propertiesApp.setPort(5672); @@ -26,7 +26,7 @@ public AsyncPropsDomainProperties customDomainProperties() { propertiesAccounts.setUsername("guest"); propertiesAccounts.setPassword("guest"); - return AsyncPropsDomainProperties.builder() + return AsyncRabbitPropsDomainProperties.builder() .withDomain("app", AsyncProps.builder() .connectionProperties(propertiesApp) .build()) diff --git a/starters/async-kafka-starter/async-kafka-starter.gradle b/starters/async-kafka-starter/async-kafka-starter.gradle index e5edebab..41ba255e 100644 --- a/starters/async-kafka-starter/async-kafka-starter.gradle +++ b/starters/async-kafka-starter/async-kafka-starter.gradle @@ -5,6 +5,7 @@ ext { dependencies { api project(':async-kafka') + api project(':shared-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-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 new file mode 100644 index 00000000..83d0425b --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/ConnectionManager.java @@ -0,0 +1,78 @@ +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/DomainHandlers.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/DomainHandlers.java new file mode 100644 index 00000000..002a7605 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/DomainHandlers.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.kafka.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-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java new file mode 100644 index 00000000..e39782e5 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java @@ -0,0 +1,8 @@ +package org.reactivecommons.async.kafka.config; + + +import org.reactivecommons.async.kafka.config.spring.KafkaPropertiesBase; + +public class KafkaProperties extends KafkaPropertiesBase { + +} \ No newline at end of file diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java new file mode 100644 index 00000000..a80b8e75 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java @@ -0,0 +1,12 @@ +package org.reactivecommons.async.kafka.config; + + +import org.reactivecommons.async.kafka.config.spring.KafkaPropertiesBase; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.kafka") +public class KafkaPropertiesAutoConfig extends KafkaPropertiesBase { + public KafkaPropertiesAutoConfig() { +// put("bootstrap.servers", "localhost:9092"); + } +} \ No newline at end of file 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 index 4ecfd4ad..8e023551 100644 --- 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 @@ -1,14 +1,17 @@ package org.reactivecommons.async.kafka.config; import org.apache.kafka.clients.admin.AdminClient; -import org.apache.kafka.clients.producer.ProducerConfig; 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; @@ -19,14 +22,18 @@ 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.RCAsyncPropsKafka; -import org.reactivecommons.async.kafka.config.props.RCKafkaProps; +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.beans.factory.annotation.Value; 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; @@ -34,64 +41,86 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; -import static org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG; -import static org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG; -import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; -import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @Configuration -@EnableConfigurationProperties({RCAsyncPropsKafka.class}) +@EnableConfigurationProperties({KafkaPropertiesAutoConfig.class, AsyncKafkaPropsDomainProperties.class}) +@Import(AsyncKafkaPropsDomain.class) // RabbitHealthConfig.class public class RCKafkaConfig { - // Sender + @Bean - @ConditionalOnMissingBean(DomainEventBus.class) - public DomainEventBus kafkaDomainEventBus(ReactiveMessageSender sender) { - return new KafkaDomainEventBus(sender); + 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; } @Bean - @ConditionalOnMissingBean(ReactiveMessageSender.class) - public ReactiveMessageSender kafkaReactiveMessageSender(KafkaSender kafkaSender, - MessageConverter converter, TopologyCreator topologyCreator) { - return new ReactiveMessageSender(kafkaSender, converter, topologyCreator); + 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; } + + // Sender @Bean - @ConditionalOnMissingBean(KafkaSender.class) - public KafkaSender kafkaSender(RCAsyncPropsKafka config, @Value("${spring.application.name}") String clientId) { - RCKafkaProps props = config.getKafkaProps(); - props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId); - props.put(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - props.put(VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); - SenderOptions senderOptions = SenderOptions.create(props); - return KafkaSender.create(senderOptions); + @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 - @Bean - @ConditionalOnMissingBean(ReactiveMessageListener.class) - public ReactiveMessageListener kafkaReactiveMessageListener(ReceiverOptions receiverOptions) { + 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); } - @Bean - @ConditionalOnMissingBean(ReceiverOptions.class) - public ReceiverOptions kafkaReceiverOptions(RCAsyncPropsKafka config) { - RCKafkaProps props = config.getKafkaProps(); - props.put(KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); - props.put(VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class); - return ReceiverOptions.create(props); - } - // Shared - - @Bean - @ConditionalOnMissingBean(TopologyCreator.class) - public TopologyCreator kafkaTopologyCreator(RCAsyncPropsKafka config, KafkaCustomizations customizations) { - AdminClient adminClient = AdminClient.create(config.getKafkaProps()); - return new TopologyCreator(adminClient, customizations); + 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 @@ -124,18 +153,38 @@ 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 RCKafkaProps readPropsFromDotEnv(Path path) throws IOException { + public static KafkaProperties readPropsFromDotEnv(Path path) throws IOException { String env = Files.readString(path); String[] split = env.split("\n"); - RCKafkaProps props = new RCKafkaProps(); + KafkaProperties props = new KafkaProperties(); + Map properties = props.getProperties(); for (String s : split) { if (s.startsWith("#")) { continue; } String[] split1 = s.split("=", 2); - props.put(split1[0], split1[1]); + properties.put(split1[0], split1[1]); } return props; } 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 index 3e638095..e3930d40 100644 --- 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 @@ -1,60 +1,51 @@ package org.reactivecommons.async.kafka.config; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.HandlerResolver; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.commons.utils.resolver.HandlerResolverUtil; -import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; -import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; -import org.reactivecommons.async.kafka.config.props.RCAsyncPropsKafka; +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.beans.factory.annotation.Value; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import reactor.core.publisher.Mono; -import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @Configuration +@RequiredArgsConstructor public class RCKafkaEventListenerConfig { @Bean - public ApplicationEventListener applicationEventListener(ReactiveMessageListener listener, - HandlerResolver resolver, - MessageConverter messageConverter, - TopologyCreator creator, - DiscardNotifier discardNotifier, - CustomReporter customReporter, - RCAsyncPropsKafka props, - @Value("${spring.application.name}") String appName) { - ApplicationEventListener eventListener = new ApplicationEventListener(listener, - resolver, - messageConverter, - props.getWithDLQRetry(), - props.getCreateTopology(), - props.getMaxRetries(), - props.getRetryDelay(), - discardNotifier, - customReporter, - appName); - - eventListener.startListener(creator); - - return eventListener; - } - - @Bean - public HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { - final Map registries = context.getBeansOfType(HandlerRegistry.class); - return HandlerResolverUtil.fromHandlerRegistries(registries.values(), defaultCommandHandler); + 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(); } - @Bean - public DefaultCommandHandler defaultCommandHandler() { - return command -> Mono.empty(); - } } 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 index d9532cef..eb84c021 100644 --- 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 @@ -1,60 +1,42 @@ package org.reactivecommons.async.kafka.config; import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.HandlerRegistry; -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.commons.utils.resolver.HandlerResolverUtil; -import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; -import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; -import org.reactivecommons.async.kafka.config.props.RCAsyncPropsKafka; +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.beans.factory.annotation.Value; -import org.springframework.context.ApplicationContext; +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 java.util.Map; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @Configuration public class RCKafkaNotificationEventListenerConfig { @Bean - public ApplicationNotificationsListener applicationEventListener(ReactiveMessageListener listener, - HandlerResolver resolver, - MessageConverter messageConverter, - TopologyCreator creator, - DiscardNotifier discardNotifier, - CustomReporter customReporter, - RCAsyncPropsKafka props, - @Value("${spring.application.name}") String appName) { - ApplicationNotificationsListener eventListener = new ApplicationNotificationsListener(listener, - resolver, + 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(), - discardNotifier, + manager.getDiscardNotifier(DEFAULT_DOMAIN), customReporter, - appName); + props.getAppName()); - eventListener.startListener(creator); + eventListener.startListener(manager.getTopologyCreator(DEFAULT_DOMAIN)); return eventListener; } - - @Bean - public HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { - final Map registries = context.getBeansOfType(HandlerRegistry.class); - return HandlerResolverUtil.fromHandlerRegistries(registries.values(), defaultCommandHandler); - } - - @Bean - public DefaultCommandHandler defaultCommandHandler() { - return command -> Mono.empty(); - } } 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 new file mode 100644 index 00000000..1636c90e --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java @@ -0,0 +1,46 @@ +package org.reactivecommons.async.kafka.config.props; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.starter.GenericAsyncProps; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class AsyncKafkaProps extends GenericAsyncProps { + + @NestedConfigurationProperty + private KafkaProperties connectionProperties = new KafkaProperties(); + + @NestedConfigurationProperty + @Builder.Default + private DomainProps domain = new DomainProps(); + + /** + * -1 will be considered default value. + * When withDLQRetry is true, it will be retried 10 times. + * When withDLQRetry is false, it will be retried indefinitely. + */ + @Builder.Default + private Integer maxRetries = -1; + + @Builder.Default + private Integer retryDelay = 1000; + + @Builder.Default + private Boolean withDLQRetry = false; + @Builder.Default + private Boolean createTopology = true; + @Builder.Default + private Boolean checkExistingTopics = true; + +} 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 new file mode 100644 index 00000000..fb50627b --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java @@ -0,0 +1,35 @@ +package org.reactivecommons.async.kafka.config.props; + +import lombok.Getter; +import lombok.Setter; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.starter.GenericAsyncPropsDomain; +import org.springframework.beans.factory.annotation.Value; + +import java.lang.reflect.Constructor; + +@Getter +@Setter +public class AsyncKafkaPropsDomain extends GenericAsyncPropsDomain { + + public AsyncKafkaPropsDomain(@Value("${spring.application.name}") String defaultAppName, + KafkaProperties defaultKafkaProperties, + AsyncKafkaPropsDomainProperties configured, + KafkaSecretFiller kafkaSecretFiller) { + super(defaultAppName, defaultKafkaProperties, configured, kafkaSecretFiller, AsyncKafkaProps.class, + KafkaProperties.class); + } + + @SuppressWarnings("unchecked") + public static AsyncPropsDomainBuilder builder() { + return GenericAsyncPropsDomain.builder(AsyncKafkaProps.class, + KafkaProperties.class, + AsyncKafkaPropsDomainProperties.class, + (Constructor) AsyncKafkaPropsDomain.class.getDeclaredConstructors()[0]); + } + + public interface KafkaSecretFiller extends GenericAsyncPropsDomain.SecretFiller { + } + +} 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 new file mode 100644 index 00000000..015c7cf8 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.kafka.config.props; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.starter.GenericAsyncPropsDomainProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@ConfigurationProperties(prefix = "reactive.commons.kafka") +public class AsyncKafkaPropsDomainProperties extends GenericAsyncPropsDomainProperties { + + public AsyncKafkaPropsDomainProperties(Map m) { + super(m); + } + + public AsyncKafkaPropsDomainProperties() { + } + + public static AsyncPropsDomainPropertiesBuilder builder() { + return GenericAsyncPropsDomainProperties.builder(AsyncKafkaPropsDomainProperties.class); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java new file mode 100644 index 00000000..da41f4f6 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java @@ -0,0 +1,21 @@ +package org.reactivecommons.async.kafka.config.props; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DomainProps { + // +// @NestedConfigurationProperty +// @Builder.Default +// private EventsProps events = new EventsProps(); + @Builder.Default + private boolean ignoreThisListener = false; +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java deleted file mode 100644 index b09e7b46..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCAsyncPropsKafka.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.reactivecommons.async.kafka.config.props; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - - -@Getter -@Setter -@ConfigurationProperties(prefix = "reactive.commons.kafka") -public class RCAsyncPropsKafka { - - @NestedConfigurationProperty - private RCKafkaProps kafkaProps = new RCKafkaProps(); - - /** - * -1 will be considered default value. - * When withDLQRetry is true, it will be retried 10 times. - * When withDLQRetry is false, it will be retried indefinitely. - */ - private Integer maxRetries = -1; - - private Integer retryDelay = 1000; - - private Boolean withDLQRetry = false; - private Boolean createTopology = true; -} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java deleted file mode 100644 index 9e7e1cd3..00000000 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/RCKafkaProps.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.reactivecommons.async.kafka.config.props; - -import java.util.HashMap; - -public class RCKafkaProps extends HashMap { -} 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 new file mode 100644 index 00000000..ca677543 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java @@ -0,0 +1,1711 @@ +/* +Copied from https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java + */ + +package org.reactivecommons.async.kafka.config.spring; + +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.config.SslConfigs; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.boot.autoconfigure.kafka.SslBundleSslEngineFactory; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; +import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.core.io.Resource; +import org.springframework.util.CollectionUtils; +import org.springframework.util.unit.DataSize; + +import java.io.IOException; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Configuration properties for Spring for Apache Kafka. + *

+ * Users should refer to Kafka documentation for complete descriptions of these + * properties. + * + * @author Gary Russell + * @author Stephane Nicoll + * @author Artem Bilan + * @author Nakul Mishra + * @author Tomaz Fernandes + * @author Andy Wilkinson + * @author Scott Frederick + * @since 1.5.0 + */ +public class KafkaPropertiesBase { + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Applies to all components unless overridden. + */ + private List bootstrapServers = new ArrayList<>(Collections.singletonList("localhost:9092")); + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Additional properties, common to producers and consumers, used to configure the + * client. + */ + private final Map properties = new HashMap<>(); + + private final Consumer consumer = new Consumer(); + + private final Producer producer = new Producer(); + + private final Admin admin = new Admin(); + + private final Streams streams = new Streams(); + + private final Listener listener = new Listener(); + + private final Ssl ssl = new Ssl(); + + private final Jaas jaas = new Jaas(); + + private final Template template = new Template(); + + private final Security security = new Security(); + + private final Retry retry = new Retry(); + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Map getProperties() { + return this.properties; + } + + public Consumer getConsumer() { + return this.consumer; + } + + public Producer getProducer() { + return this.producer; + } + + public Listener getListener() { + return this.listener; + } + + public Admin getAdmin() { + return this.admin; + } + + public Streams getStreams() { + return this.streams; + } + + public Ssl getSsl() { + return this.ssl; + } + + public Jaas getJaas() { + return this.jaas; + } + + public Template getTemplate() { + return this.template; + } + + public Security getSecurity() { + return this.security; + } + + public Retry getRetry() { + return this.retry; + } + + private Map buildCommonProperties(SslBundles sslBundles) { + Map properties = new HashMap<>(); + if (this.bootstrapServers != null) { + properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers); + } + if (this.clientId != null) { + properties.put(CommonClientConfigs.CLIENT_ID_CONFIG, this.clientId); + } + properties.putAll(this.ssl.buildProperties(sslBundles)); + properties.putAll(this.security.buildProperties()); + if (!CollectionUtils.isEmpty(this.properties)) { + properties.putAll(this.properties); + } + return properties; + } + + /** + * Create an initial map of consumer properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary, and override the + * default {@code kafkaConsumerFactory} bean. + * + * @param sslBundles bundles providing SSL trust material + * @return the consumer properties initialized with the customizations defined on this + * instance + */ + public Map buildConsumerProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.consumer.buildProperties(sslBundles)); + return properties; + } + + /** + * Create an initial map of producer properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary, and override the + * default {@code kafkaProducerFactory} bean. + * + * @param sslBundles bundles providing SSL trust material + * @return the producer properties initialized with the customizations defined on this + * instance + */ + public Map buildProducerProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.producer.buildProperties(sslBundles)); + return properties; + } + + /** + * Create an initial map of admin properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary, and override the + * default {@code kafkaAdmin} bean. + * + * @param sslBundles bundles providing SSL trust material + * @return the admin properties initialized with the customizations defined on this + * instance + */ + public Map buildAdminProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.admin.buildProperties(sslBundles)); + return properties; + } + + /** + * Create an initial map of streams properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary. + * + * @param sslBundles bundles providing SSL trust material + * @return the streams properties initialized with the customizations defined on this + * instance + */ + public Map buildStreamsProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.streams.buildProperties(sslBundles)); + return properties; + } + + public static class Consumer { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + /** + * Frequency with which the consumer offsets are auto-committed to Kafka if + * 'enable.auto.commit' is set to true. + */ + private Duration autoCommitInterval; + + /** + * What to do when there is no initial offset in Kafka or if the current offset no + * longer exists on the server. + */ + private String autoOffsetReset; + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Overrides the global property, for consumers. + */ + private List bootstrapServers; + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Whether the consumer's offset is periodically committed in the background. + */ + private Boolean enableAutoCommit; + + /** + * Maximum amount of time the server blocks before answering the fetch request if + * there isn't sufficient data to immediately satisfy the requirement given by + * "fetch-min-size". + */ + private Duration fetchMaxWait; + + /** + * Minimum amount of data the server should return for a fetch request. + */ + private DataSize fetchMinSize; + + /** + * Unique string that identifies the consumer group to which this consumer + * belongs. + */ + private String groupId; + + /** + * Expected time between heartbeats to the consumer coordinator. + */ + private Duration heartbeatInterval; + + /** + * Isolation level for reading messages that have been written transactionally. + */ + private IsolationLevel isolationLevel = IsolationLevel.READ_UNCOMMITTED; + + /** + * Deserializer class for keys. + */ + private Class keyDeserializer = StringDeserializer.class; + + /** + * Deserializer class for values. + */ + private Class valueDeserializer = StringDeserializer.class; + + /** + * Maximum number of records returned in a single call to poll(). + */ + private Integer maxPollRecords; + + /** + * Additional consumer-specific properties used to configure the client. + */ + private final Map properties = new HashMap<>(); + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public Duration getAutoCommitInterval() { + return this.autoCommitInterval; + } + + public void setAutoCommitInterval(Duration autoCommitInterval) { + this.autoCommitInterval = autoCommitInterval; + } + + public String getAutoOffsetReset() { + return this.autoOffsetReset; + } + + public void setAutoOffsetReset(String autoOffsetReset) { + this.autoOffsetReset = autoOffsetReset; + } + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Boolean getEnableAutoCommit() { + return this.enableAutoCommit; + } + + public void setEnableAutoCommit(Boolean enableAutoCommit) { + this.enableAutoCommit = enableAutoCommit; + } + + public Duration getFetchMaxWait() { + return this.fetchMaxWait; + } + + public void setFetchMaxWait(Duration fetchMaxWait) { + this.fetchMaxWait = fetchMaxWait; + } + + public DataSize getFetchMinSize() { + return this.fetchMinSize; + } + + public void setFetchMinSize(DataSize fetchMinSize) { + this.fetchMinSize = fetchMinSize; + } + + public String getGroupId() { + return this.groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public Duration getHeartbeatInterval() { + return this.heartbeatInterval; + } + + public void setHeartbeatInterval(Duration heartbeatInterval) { + this.heartbeatInterval = heartbeatInterval; + } + + public IsolationLevel getIsolationLevel() { + return this.isolationLevel; + } + + public void setIsolationLevel(IsolationLevel isolationLevel) { + this.isolationLevel = isolationLevel; + } + + public Class getKeyDeserializer() { + return this.keyDeserializer; + } + + public void setKeyDeserializer(Class keyDeserializer) { + this.keyDeserializer = keyDeserializer; + } + + public Class getValueDeserializer() { + return this.valueDeserializer; + } + + public void setValueDeserializer(Class valueDeserializer) { + this.valueDeserializer = valueDeserializer; + } + + public Integer getMaxPollRecords() { + return this.maxPollRecords; + } + + public void setMaxPollRecords(Integer maxPollRecords) { + this.maxPollRecords = maxPollRecords; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getAutoCommitInterval) + .asInt(Duration::toMillis) + .to(properties.in(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG)); + map.from(this::getAutoOffsetReset).to(properties.in(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG)); + map.from(this::getBootstrapServers).to(properties.in(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)); + map.from(this::getClientId).to(properties.in(ConsumerConfig.CLIENT_ID_CONFIG)); + map.from(this::getEnableAutoCommit).to(properties.in(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG)); + map.from(this::getFetchMaxWait) + .asInt(Duration::toMillis) + .to(properties.in(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG)); + map.from(this::getFetchMinSize) + .asInt(DataSize::toBytes) + .to(properties.in(ConsumerConfig.FETCH_MIN_BYTES_CONFIG)); + map.from(this::getGroupId).to(properties.in(ConsumerConfig.GROUP_ID_CONFIG)); + map.from(this::getHeartbeatInterval) + .asInt(Duration::toMillis) + .to(properties.in(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG)); + map.from(() -> getIsolationLevel().name().toLowerCase(Locale.ROOT)) + .to(properties.in(ConsumerConfig.ISOLATION_LEVEL_CONFIG)); + map.from(this::getKeyDeserializer).to(properties.in(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG)); + map.from(this::getValueDeserializer).to(properties.in(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG)); + map.from(this::getMaxPollRecords).to(properties.in(ConsumerConfig.MAX_POLL_RECORDS_CONFIG)); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + public static class Producer { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + /** + * Number of acknowledgments the producer requires the leader to have received + * before considering a request complete. + */ + private String acks; + + /** + * Default batch size. A small batch size will make batching less common and may + * reduce throughput (a batch size of zero disables batching entirely). + */ + private DataSize batchSize; + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Overrides the global property, for producers. + */ + private List bootstrapServers; + + /** + * Total memory size the producer can use to buffer records waiting to be sent to + * the server. + */ + private DataSize bufferMemory; + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Compression type for all data generated by the producer. + */ + private String compressionType; + + /** + * Serializer class for keys. + */ + private Class keySerializer = StringSerializer.class; + + /** + * Serializer class for values. + */ + private Class valueSerializer = StringSerializer.class; + + /** + * When greater than zero, enables retrying of failed sends. + */ + private Integer retries; + + /** + * When non empty, enables transaction support for producer. + */ + private String transactionIdPrefix; + + /** + * Additional producer-specific properties used to configure the client. + */ + private final Map properties = new HashMap<>(); + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public String getAcks() { + return this.acks; + } + + public void setAcks(String acks) { + this.acks = acks; + } + + public DataSize getBatchSize() { + return this.batchSize; + } + + public void setBatchSize(DataSize batchSize) { + this.batchSize = batchSize; + } + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public DataSize getBufferMemory() { + return this.bufferMemory; + } + + public void setBufferMemory(DataSize bufferMemory) { + this.bufferMemory = bufferMemory; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getCompressionType() { + return this.compressionType; + } + + public void setCompressionType(String compressionType) { + this.compressionType = compressionType; + } + + public Class getKeySerializer() { + return this.keySerializer; + } + + public void setKeySerializer(Class keySerializer) { + this.keySerializer = keySerializer; + } + + public Class getValueSerializer() { + return this.valueSerializer; + } + + public void setValueSerializer(Class valueSerializer) { + this.valueSerializer = valueSerializer; + } + + public Integer getRetries() { + return this.retries; + } + + public void setRetries(Integer retries) { + this.retries = retries; + } + + public String getTransactionIdPrefix() { + return this.transactionIdPrefix; + } + + public void setTransactionIdPrefix(String transactionIdPrefix) { + this.transactionIdPrefix = transactionIdPrefix; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getAcks).to(properties.in(ProducerConfig.ACKS_CONFIG)); + map.from(this::getBatchSize).asInt(DataSize::toBytes).to(properties.in(ProducerConfig.BATCH_SIZE_CONFIG)); + map.from(this::getBootstrapServers).to(properties.in(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG)); + map.from(this::getBufferMemory) + .as(DataSize::toBytes) + .to(properties.in(ProducerConfig.BUFFER_MEMORY_CONFIG)); + map.from(this::getClientId).to(properties.in(ProducerConfig.CLIENT_ID_CONFIG)); + map.from(this::getCompressionType).to(properties.in(ProducerConfig.COMPRESSION_TYPE_CONFIG)); + map.from(this::getKeySerializer).to(properties.in(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG)); + map.from(this::getRetries).to(properties.in(ProducerConfig.RETRIES_CONFIG)); + map.from(this::getValueSerializer).to(properties.in(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG)); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + public static class Admin { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Additional admin-specific properties used to configure the client. + */ + private final Map properties = new HashMap<>(); + + /** + * Close timeout. + */ + private Duration closeTimeout; + + /** + * Operation timeout. + */ + private Duration operationTimeout; + + /** + * Whether to fail fast if the broker is not available on startup. + */ + private boolean failFast; + + /** + * Whether to enable modification of existing topic configuration. + */ + private boolean modifyTopicConfigs; + + /** + * Whether to automatically create topics during context initialization. When set + * to false, disables automatic topic creation during context initialization. + */ + private boolean autoCreate = true; + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Duration getCloseTimeout() { + return this.closeTimeout; + } + + public void setCloseTimeout(Duration closeTimeout) { + this.closeTimeout = closeTimeout; + } + + public Duration getOperationTimeout() { + return this.operationTimeout; + } + + public void setOperationTimeout(Duration operationTimeout) { + this.operationTimeout = operationTimeout; + } + + public boolean isFailFast() { + return this.failFast; + } + + public void setFailFast(boolean failFast) { + this.failFast = failFast; + } + + public boolean isModifyTopicConfigs() { + return this.modifyTopicConfigs; + } + + public void setModifyTopicConfigs(boolean modifyTopicConfigs) { + this.modifyTopicConfigs = modifyTopicConfigs; + } + + public boolean isAutoCreate() { + return this.autoCreate; + } + + public void setAutoCreate(boolean autoCreate) { + this.autoCreate = autoCreate; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getClientId).to(properties.in(ProducerConfig.CLIENT_ID_CONFIG)); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + /** + * High (and some medium) priority Streams properties and a general properties bucket. + */ + public static class Streams { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + private final Cleanup cleanup = new Cleanup(); + + /** + * Kafka streams application.id property; default spring.application.name. + */ + private String applicationId; + + /** + * Whether to auto-start the streams factory bean. + */ + private boolean autoStartup = true; + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Overrides the global property, for streams. + */ + private List bootstrapServers; + + /** + * Maximum size of the in-memory state store cache across all threads. + */ + private DataSize stateStoreCacheMaxSize; + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * The replication factor for change log topics and repartition topics created by + * the stream processing application. + */ + private Integer replicationFactor; + + /** + * Directory location for the state store. + */ + private String stateDir; + + /** + * Additional Kafka properties used to configure the streams. + */ + private final Map properties = new HashMap<>(); + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public Cleanup getCleanup() { + return this.cleanup; + } + + public String getApplicationId() { + return this.applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public boolean isAutoStartup() { + return this.autoStartup; + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public DataSize getStateStoreCacheMaxSize() { + return this.stateStoreCacheMaxSize; + } + + public void setStateStoreCacheMaxSize(DataSize stateStoreCacheMaxSize) { + this.stateStoreCacheMaxSize = stateStoreCacheMaxSize; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Integer getReplicationFactor() { + return this.replicationFactor; + } + + public void setReplicationFactor(Integer replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public String getStateDir() { + return this.stateDir; + } + + public void setStateDir(String stateDir) { + this.stateDir = stateDir; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getApplicationId).to(properties.in("application.id")); + map.from(this::getBootstrapServers).to(properties.in(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG)); + map.from(this::getStateStoreCacheMaxSize) + .asInt(DataSize::toBytes) + .to(properties.in("statestore.cache.max.bytes")); + map.from(this::getClientId).to(properties.in(CommonClientConfigs.CLIENT_ID_CONFIG)); + map.from(this::getReplicationFactor).to(properties.in("replication.factor")); + map.from(this::getStateDir).to(properties.in("state.dir")); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + public static class Template { + + /** + * Default topic to which messages are sent. + */ + private String defaultTopic; + + /** + * Transaction id prefix, override the transaction id prefix in the producer + * factory. + */ + private String transactionIdPrefix; + + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + + public String getDefaultTopic() { + return this.defaultTopic; + } + + public void setDefaultTopic(String defaultTopic) { + this.defaultTopic = defaultTopic; + } + + public String getTransactionIdPrefix() { + return this.transactionIdPrefix; + } + + public void setTransactionIdPrefix(String transactionIdPrefix) { + this.transactionIdPrefix = transactionIdPrefix; + } + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + + } + + public static class Listener { + + public enum Type { + + /** + * Invokes the endpoint with one ConsumerRecord at a time. + */ + SINGLE, + + /** + * Invokes the endpoint with a batch of ConsumerRecords. + */ + BATCH + + } + + /** + * Listener type. + */ + private Type type = Type.SINGLE; + + /** + * Support for asynchronous record acknowledgements. Only applies when + * spring.kafka.listener.ack-mode is manual or manual-immediate. + */ + private Boolean asyncAcks; + + /** + * Prefix for the listener's consumer client.id property. + */ + private String clientId; + + /** + * Number of threads to run in the listener containers. + */ + private Integer concurrency; + + /** + * Timeout to use when polling the consumer. + */ + private Duration pollTimeout; + + /** + * Multiplier applied to "pollTimeout" to determine if a consumer is + * non-responsive. + */ + private Float noPollThreshold; + + /** + * Number of records between offset commits when ackMode is "COUNT" or + * "COUNT_TIME". + */ + private Integer ackCount; + + /** + * Time between offset commits when ackMode is "TIME" or "COUNT_TIME". + */ + private Duration ackTime; + + /** + * Sleep interval between Consumer.poll(Duration) calls. + */ + private Duration idleBetweenPolls = Duration.ZERO; + + /** + * Time between publishing idle consumer events (no data received). + */ + private Duration idleEventInterval; + + /** + * Time between publishing idle partition consumer events (no data received for + * partition). + */ + private Duration idlePartitionEventInterval; + + /** + * Time between checks for non-responsive consumers. If a duration suffix is not + * specified, seconds will be used. + */ + @DurationUnit(ChronoUnit.SECONDS) + private Duration monitorInterval; + + /** + * Whether to log the container configuration during initialization (INFO level). + */ + private Boolean logContainerConfig; + + /** + * Whether the container should fail to start if at least one of the configured + * topics are not present on the broker. + */ + private boolean missingTopicsFatal = false; + + /** + * Whether the container stops after the current record is processed or after all + * the records from the previous poll are processed. + */ + private boolean immediateStop = false; + + /** + * Whether to auto start the container. + */ + private boolean autoStartup = true; + + /** + * Whether to instruct the container to change the consumer thread name during + * initialization. + */ + private Boolean changeConsumerThreadName; + + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + + public Type getType() { + return this.type; + } + + public void setType(Type type) { + this.type = type; + } + + public Boolean getAsyncAcks() { + return this.asyncAcks; + } + + public void setAsyncAcks(Boolean asyncAcks) { + this.asyncAcks = asyncAcks; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Integer getConcurrency() { + return this.concurrency; + } + + public void setConcurrency(Integer concurrency) { + this.concurrency = concurrency; + } + + public Duration getPollTimeout() { + return this.pollTimeout; + } + + public void setPollTimeout(Duration pollTimeout) { + this.pollTimeout = pollTimeout; + } + + public Float getNoPollThreshold() { + return this.noPollThreshold; + } + + public void setNoPollThreshold(Float noPollThreshold) { + this.noPollThreshold = noPollThreshold; + } + + public Integer getAckCount() { + return this.ackCount; + } + + public void setAckCount(Integer ackCount) { + this.ackCount = ackCount; + } + + public Duration getAckTime() { + return this.ackTime; + } + + public void setAckTime(Duration ackTime) { + this.ackTime = ackTime; + } + + public Duration getIdleBetweenPolls() { + return this.idleBetweenPolls; + } + + public void setIdleBetweenPolls(Duration idleBetweenPolls) { + this.idleBetweenPolls = idleBetweenPolls; + } + + public Duration getIdleEventInterval() { + return this.idleEventInterval; + } + + public void setIdleEventInterval(Duration idleEventInterval) { + this.idleEventInterval = idleEventInterval; + } + + public Duration getIdlePartitionEventInterval() { + return this.idlePartitionEventInterval; + } + + public void setIdlePartitionEventInterval(Duration idlePartitionEventInterval) { + this.idlePartitionEventInterval = idlePartitionEventInterval; + } + + public Duration getMonitorInterval() { + return this.monitorInterval; + } + + public void setMonitorInterval(Duration monitorInterval) { + this.monitorInterval = monitorInterval; + } + + public Boolean getLogContainerConfig() { + return this.logContainerConfig; + } + + public void setLogContainerConfig(Boolean logContainerConfig) { + this.logContainerConfig = logContainerConfig; + } + + public boolean isMissingTopicsFatal() { + return this.missingTopicsFatal; + } + + public void setMissingTopicsFatal(boolean missingTopicsFatal) { + this.missingTopicsFatal = missingTopicsFatal; + } + + public boolean isImmediateStop() { + return this.immediateStop; + } + + public void setImmediateStop(boolean immediateStop) { + this.immediateStop = immediateStop; + } + + public boolean isAutoStartup() { + return this.autoStartup; + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + public Boolean getChangeConsumerThreadName() { + return this.changeConsumerThreadName; + } + + public void setChangeConsumerThreadName(Boolean changeConsumerThreadName) { + this.changeConsumerThreadName = changeConsumerThreadName; + } + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + + } + + public static class Ssl { + + /** + * Name of the SSL bundle to use. + */ + private String bundle; + + /** + * Password of the private key in either key store key or key store file. + */ + private String keyPassword; + + /** + * Certificate chain in PEM format with a list of X.509 certificates. + */ + private String keyStoreCertificateChain; + + /** + * Private key in PEM format with PKCS#8 keys. + */ + private String keyStoreKey; + + /** + * Location of the key store file. + */ + private Resource keyStoreLocation; + + /** + * Store password for the key store file. + */ + private String keyStorePassword; + + /** + * Type of the key store. + */ + private String keyStoreType; + + /** + * Trusted certificates in PEM format with X.509 certificates. + */ + private String trustStoreCertificates; + + /** + * Location of the trust store file. + */ + private Resource trustStoreLocation; + + /** + * Store password for the trust store file. + */ + private String trustStorePassword; + + /** + * Type of the trust store. + */ + private String trustStoreType; + + /** + * SSL protocol to use. + */ + private String protocol; + + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + public String getKeyPassword() { + return this.keyPassword; + } + + public void setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + } + + public String getKeyStoreCertificateChain() { + return this.keyStoreCertificateChain; + } + + public void setKeyStoreCertificateChain(String keyStoreCertificateChain) { + this.keyStoreCertificateChain = keyStoreCertificateChain; + } + + public String getKeyStoreKey() { + return this.keyStoreKey; + } + + public void setKeyStoreKey(String keyStoreKey) { + this.keyStoreKey = keyStoreKey; + } + + public Resource getKeyStoreLocation() { + return this.keyStoreLocation; + } + + public void setKeyStoreLocation(Resource keyStoreLocation) { + this.keyStoreLocation = keyStoreLocation; + } + + public String getKeyStorePassword() { + return this.keyStorePassword; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public String getKeyStoreType() { + return this.keyStoreType; + } + + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public String getTrustStoreCertificates() { + return this.trustStoreCertificates; + } + + public void setTrustStoreCertificates(String trustStoreCertificates) { + this.trustStoreCertificates = trustStoreCertificates; + } + + public Resource getTrustStoreLocation() { + return this.trustStoreLocation; + } + + public void setTrustStoreLocation(Resource trustStoreLocation) { + this.trustStoreLocation = trustStoreLocation; + } + + public String getTrustStorePassword() { + return this.trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public String getTrustStoreType() { + return this.trustStoreType; + } + + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + public String getProtocol() { + return this.protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + @Deprecated(since = "3.2.0", forRemoval = true) + public Map buildProperties() { + return buildProperties(null); + } + + public Map buildProperties(SslBundles sslBundles) { + validate(); + Properties properties = new Properties(); + if (getBundle() != null) { + properties.in(SslConfigs.SSL_ENGINE_FACTORY_CLASS_CONFIG) + .accept(SslBundleSslEngineFactory.class.getName()); + properties.in(SslBundle.class.getName()).accept(sslBundles.getBundle(getBundle())); + } else { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getKeyPassword).to(properties.in(SslConfigs.SSL_KEY_PASSWORD_CONFIG)); + map.from(this::getKeyStoreCertificateChain) + .to(properties.in(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG)); + map.from(this::getKeyStoreKey).to(properties.in(SslConfigs.SSL_KEYSTORE_KEY_CONFIG)); + map.from(this::getKeyStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG)); + map.from(this::getKeyStorePassword).to(properties.in(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG)); + map.from(this::getKeyStoreType).to(properties.in(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG)); + map.from(this::getTrustStoreCertificates) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG)); + map.from(this::getTrustStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); + map.from(this::getTrustStorePassword).to(properties.in(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); + map.from(this::getTrustStoreType).to(properties.in(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG)); + map.from(this::getProtocol).to(properties.in(SslConfigs.SSL_PROTOCOL_CONFIG)); + } + return properties; + } + + private void validate() { + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); + entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); + entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); + }); + } + + private String resourceToPath(Resource resource) { + try { + return resource.getFile().getAbsolutePath(); + } catch (IOException ex) { + throw new IllegalStateException("Resource '" + resource + "' must be on a file system", ex); + } + } + + } + + public static class Jaas { + + /** + * Whether to enable JAAS configuration. + */ + private boolean enabled; + + /** + * Login module. + */ + private String loginModule = "com.sun.security.auth.module.Krb5LoginModule"; + + /** + * Additional JAAS options. + */ + private final Map options = new HashMap<>(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getLoginModule() { + return this.loginModule; + } + + public void setLoginModule(String loginModule) { + this.loginModule = loginModule; + } + + public Map getOptions() { + return this.options; + } + + public void setOptions(Map options) { + if (options != null) { + this.options.putAll(options); + } + } + + } + + public static class Security { + + /** + * Security protocol used to communicate with brokers. + */ + private String protocol; + + public String getProtocol() { + return this.protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public Map buildProperties() { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getProtocol).to(properties.in(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG)); + return properties; + } + + } + + public static class Retry { + + private final Topic topic = new Topic(); + + public Topic getTopic() { + return this.topic; + } + + /** + * Properties for non-blocking, topic-based retries. + */ + public static class Topic { + + /** + * Whether to enable topic-based non-blocking retries. + */ + private boolean enabled; + + /** + * Total number of processing attempts made before sending the message to the + * DLT. + */ + private int attempts = 3; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getAttempts() { + return this.attempts; + } + + public void setAttempts(int attempts) { + this.attempts = attempts; + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.delay", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public Duration getDelay() { + return getBackoff().getDelay(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setDelay(Duration delay) { + getBackoff().setDelay(delay); + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.multiplier", + since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public double getMultiplier() { + return getBackoff().getMultiplier(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setMultiplier(double multiplier) { + getBackoff().setMultiplier(multiplier); + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.maxDelay", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public Duration getMaxDelay() { + return getBackoff().getMaxDelay(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setMaxDelay(Duration maxDelay) { + getBackoff().setMaxDelay(maxDelay); + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.random", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public boolean isRandomBackOff() { + return getBackoff().isRandom(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setRandomBackOff(boolean randomBackOff) { + getBackoff().setRandom(randomBackOff); + } + + private final Backoff backoff = new Backoff(); + + public Backoff getBackoff() { + return this.backoff; + } + + public static class Backoff { + + /** + * Canonical backoff period. Used as an initial value in the exponential + * case, and as a minimum value in the uniform case. + */ + private Duration delay = Duration.ofSeconds(1); + + /** + * Multiplier to use for generating the next backoff delay. + */ + private double multiplier = 0.0; + + /** + * Maximum wait between retries. If less than the delay then the default + * of 30 seconds is applied. + */ + private Duration maxDelay = Duration.ZERO; + + /** + * Whether to have the backoff delays. + */ + private boolean random = false; + + public Duration getDelay() { + return this.delay; + } + + public void setDelay(Duration delay) { + this.delay = delay; + } + + public double getMultiplier() { + return this.multiplier; + } + + public void setMultiplier(double multiplier) { + this.multiplier = multiplier; + } + + public Duration getMaxDelay() { + return this.maxDelay; + } + + public void setMaxDelay(Duration maxDelay) { + this.maxDelay = maxDelay; + } + + public boolean isRandom() { + return this.random; + } + + public void setRandom(boolean random) { + this.random = random; + } + + } + + } + + } + + public static class Cleanup { + + /** + * Cleanup the application’s local state directory on startup. + */ + private boolean onStartup = false; + + /** + * Cleanup the application’s local state directory on shutdown. + */ + private boolean onShutdown = false; + + public boolean isOnStartup() { + return this.onStartup; + } + + public void setOnStartup(boolean onStartup) { + this.onStartup = onStartup; + } + + public boolean isOnShutdown() { + return this.onShutdown; + } + + public void setOnShutdown(boolean onShutdown) { + this.onShutdown = onShutdown; + } + + } + + public enum IsolationLevel { + + /** + * Read everything including aborted transactions. + */ + READ_UNCOMMITTED((byte) 0), + + /** + * Read records from committed transactions, in addition to records not part of + * transactions. + */ + READ_COMMITTED((byte) 1); + + private final byte id; + + IsolationLevel(byte id) { + this.id = id; + } + + public byte id() { + return this.id; + } + + } + + @SuppressWarnings("serial") + private static final class Properties extends HashMap { + + java.util.function.Consumer in(String key) { + return (value) -> put(key, value); + } + + Properties with(Ssl ssl, Security security, Map properties, SslBundles sslBundles) { + putAll(ssl.buildProperties(sslBundles)); + putAll(security.buildProperties()); + putAll(properties); + return this; + } + + } + +} \ No newline at end of file diff --git a/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle index 395917e7..51c58ebb 100644 --- a/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle +++ b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle @@ -5,6 +5,7 @@ ext { dependencies { api project(':async-rabbit') + api project(':shared-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/rabbit/config/EventListenersConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java index 0876112c..53466827 100644 --- 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 @@ -23,7 +23,8 @@ public class EventListenersConfig { @Bean public ApplicationEventListener eventListener(MessageConverter messageConverter, - ConnectionManager manager, DomainHandlers handlers, + ConnectionManager manager, + DomainHandlers handlers, CustomReporter errorReporter) { AtomicReference external = new AtomicReference<>(); manager.forListener((domain, receiver) -> { 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 index 5df1231a..5e4688d1 100644 --- 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 @@ -16,6 +16,7 @@ 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; @@ -30,7 +31,7 @@ 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.AsyncPropsDomainProperties; +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; @@ -61,7 +62,7 @@ @Log @Configuration @RequiredArgsConstructor -@EnableConfigurationProperties({RabbitPropertiesAutoConfig.class, AsyncPropsDomainProperties.class}) +@EnableConfigurationProperties({RabbitPropertiesAutoConfig.class, AsyncRabbitPropsDomainProperties.class}) @Import({RabbitHealthConfig.class, AsyncPropsDomain.class}) public class RabbitMqConfig { @@ -237,8 +238,8 @@ public HandlerRegistry defaultHandlerRegistry() { } @Bean - @ConditionalOnMissingBean(AsyncPropsDomain.SecretFiller.class) - public AsyncPropsDomain.SecretFiller defaultSecretFiller() { + @ConditionalOnMissingBean(AsyncPropsDomain.RabbitSecretFiller.class) + public AsyncPropsDomain.RabbitSecretFiller defaultRabbitSecretFiller() { return (ignoredDomain, ignoredProps) -> { }; } 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 f9d2ea29..4e7f0bc3 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 @@ -5,8 +5,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +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.springframework.boot.context.properties.NestedConfigurationProperty; @@ -14,11 +16,8 @@ @Setter @AllArgsConstructor @NoArgsConstructor -@Builder -public class AsyncProps { - private String appName; - private String secret; - +@SuperBuilder +public class AsyncProps extends GenericAsyncProps { @NestedConfigurationProperty @Builder.Default private FluxProps flux = new FluxProps(); 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 7da96bcf..115f7030 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 @@ -1,111 +1,41 @@ package org.reactivecommons.async.rabbit.config.props; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.Getter; import lombok.Setter; import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.rabbit.config.exceptions.InvalidConfigurationException; +import org.reactivecommons.async.starter.GenericAsyncPropsDomain; import org.springframework.beans.factory.annotation.Value; -import java.util.HashMap; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; +import java.lang.reflect.Constructor; @Getter @Setter -public class AsyncPropsDomain extends HashMap { +public class AsyncPropsDomain extends GenericAsyncPropsDomain { public AsyncPropsDomain(@Value("${spring.application.name}") String defaultAppName, RabbitProperties defaultRabbitProperties, - AsyncPropsDomainProperties configured, - SecretFiller secretFiller) { - super(configured); - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new JavaTimeModule()); - this.computeIfAbsent(DEFAULT_DOMAIN, k -> { - AsyncProps defaultApp = new AsyncProps(); - defaultApp.setConnectionProperties(mapper.convertValue(defaultRabbitProperties, RabbitProperties.class)); - return defaultApp; - }); - super.forEach((key, value) -> { // To ensure that each domain has an appName - if (value.getAppName() == null) { - if (defaultAppName == null || defaultAppName.isEmpty()) { - throw new InvalidConfigurationException("defaultAppName does not has value and domain " + key - + " has not set the property appName. please use app.async." + key + ".appName property or " + - " spring.application.name property or withDefaultAppName in builder"); - } - value.setAppName(defaultAppName); - } - if (value.getConnectionProperties() == null) { - if (defaultRabbitProperties == null) { - throw new InvalidConfigurationException("Domain " + key + " could not be instantiated because no" + - " RabbitProperties properties found, please use withDefaultRabbitProperties or define the" + - "default " + key + " domain with properties explicitly"); - } - value.setConnectionProperties(mapper.convertValue(defaultRabbitProperties, RabbitProperties.class)); - } - if (value.getBrokerConfigProps() == null) { - value.setBrokerConfigProps(new BrokerConfigProps(value)); - } - if (secretFiller != null) { - secretFiller.fillWithSecret(key, value); - } - }); + AsyncRabbitPropsDomainProperties configured, + RabbitSecretFiller secretFiller) { + super(defaultAppName, defaultRabbitProperties, configured, secretFiller, AsyncProps.class, + RabbitProperties.class); } - public AsyncProps getProps(String domain) { - AsyncProps props = get(domain); - if (props == null) { - throw new InvalidConfigurationException("Domain " + domain + " id not defined"); - } - return props; + @SuppressWarnings("unchecked") + public static AsyncPropsDomainBuilder builder() { + return GenericAsyncPropsDomain.builder(AsyncProps.class, + RabbitProperties.class, + AsyncRabbitPropsDomainProperties.class, + (Constructor) AsyncPropsDomain.class.getDeclaredConstructors()[0]); } - public static AsyncPropsDomainBuilder builder() { - return new AsyncPropsDomainBuilder(); - } - - public static class AsyncPropsDomainBuilder { - private String defaultAppName; - private RabbitProperties defaultRabbitProperties; - private SecretFiller secretFiller; - private final HashMap domains = new HashMap<>(); - - - public AsyncPropsDomainBuilder withDefaultRabbitProperties(RabbitProperties defaultRabbitProperties) { - this.defaultRabbitProperties = defaultRabbitProperties; - return this; - } - - - public AsyncPropsDomainBuilder withDefaultAppName(String defaultAppName) { - this.defaultAppName = defaultAppName; - return this; + @Override + protected void fillCustoms(AsyncProps asyncProps) { + if (asyncProps.getBrokerConfigProps() == null) { + asyncProps.setBrokerConfigProps(new BrokerConfigProps(asyncProps)); } - - - public AsyncPropsDomainBuilder withSecretFiller(SecretFiller secretFiller) { - this.secretFiller = secretFiller; - return this; - } - - public AsyncPropsDomainBuilder withDomain(String domain, AsyncProps props) { - domains.put(domain, props); - return this; - } - - public AsyncPropsDomain build() { - AsyncPropsDomainProperties domainProperties = new AsyncPropsDomainProperties(domains); - if (defaultRabbitProperties == null) { - defaultRabbitProperties = new RabbitProperties(); - } - return new AsyncPropsDomain(defaultAppName, defaultRabbitProperties, domainProperties, secretFiller); - } - } - public interface SecretFiller { - void fillWithSecret(String domain, AsyncProps props); + public interface RabbitSecretFiller extends SecretFiller { } } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java deleted file mode 100644 index 19adb140..00000000 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.HashMap; -import java.util.Map; - -@Getter -@Setter -@ConfigurationProperties(prefix = "app.async") -public class AsyncPropsDomainProperties extends HashMap { - - public AsyncPropsDomainProperties() { - } - - public AsyncPropsDomainProperties(Map m) { - super(m); - } - - public static AsyncPropsDomainPropertiesBuilder builder() { - return new AsyncPropsDomainPropertiesBuilder(); - } - - public static class AsyncPropsDomainPropertiesBuilder { - private final HashMap domains = new HashMap<>(); - - public AsyncPropsDomainPropertiesBuilder withDomain(String domain, AsyncProps props) { - domains.put(domain, props); - return this; - } - - public AsyncPropsDomainProperties build() { - return new AsyncPropsDomainProperties(domains); - } - } -} 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 new file mode 100644 index 00000000..77f05995 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java @@ -0,0 +1,24 @@ +package org.reactivecommons.async.rabbit.config.props; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.starter.GenericAsyncPropsDomainProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@ConfigurationProperties(prefix = "app.async") +public class AsyncRabbitPropsDomainProperties extends GenericAsyncPropsDomainProperties { + + + public AsyncRabbitPropsDomainProperties() { + } + + public AsyncRabbitPropsDomainProperties(Map m) { + super(m); + } + + public static AsyncPropsDomainPropertiesBuilder builder() { + return GenericAsyncPropsDomainProperties.builder(AsyncRabbitPropsDomainProperties.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 index 97f04e77..cd9169ea 100644 --- 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 @@ -6,9 +6,9 @@ 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.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; @@ -33,10 +33,10 @@ class CommandListenersConfigTest { private final AsyncProps props = new AsyncProps(); private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) + .withDefaultProperties(new RabbitProperties()) .withDomain(DEFAULT_DOMAIN, props) .build(); - private CommandListenersConfig config = new CommandListenersConfig(asyncPropsDomain); + 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); 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 index 2a77857c..82494ee8 100644 --- 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 @@ -5,9 +5,9 @@ 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.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; @@ -34,7 +34,7 @@ class EventListenersConfigTest { private final AsyncProps props = new AsyncProps(); private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) + .withDefaultProperties(new RabbitProperties()) .withDomain(DEFAULT_DOMAIN, props) .build(); private final EventListenersConfig config = new EventListenersConfig(asyncPropsDomain); 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 index e019c4b4..30f67fca 100644 --- 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 @@ -5,9 +5,9 @@ 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.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.rabbit.config.props.AsyncProps; @@ -33,7 +33,7 @@ class NotificationListenersConfigTest { private final AsyncProps props = new AsyncProps(); private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) + .withDefaultProperties(new RabbitProperties()) .withDomain(DEFAULT_DOMAIN, props) .build(); private final NotificationListenersConfig config = new NotificationListenersConfig(asyncPropsDomain); 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 index d4a61131..143f9a32 100644 --- 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 @@ -4,9 +4,9 @@ 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.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; @@ -33,7 +33,7 @@ class QueryListenerConfigTest { private final AsyncProps props = new AsyncProps(); private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) + .withDefaultProperties(new RabbitProperties()) .withDomain(DEFAULT_DOMAIN, props) .build(); private final QueryListenerConfig config = new QueryListenerConfig(asyncPropsDomain); diff --git a/starters/shared/shared-starter.gradle b/starters/shared/shared-starter.gradle new file mode 100644 index 00000000..c135d06e --- /dev/null +++ b/starters/shared/shared-starter.gradle @@ -0,0 +1,10 @@ +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 diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java b/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java new file mode 100644 index 00000000..f2b56b92 --- /dev/null +++ b/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncProps.java @@ -0,0 +1,21 @@ +package org.reactivecommons.async.starter; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public abstract class GenericAsyncProps

{ + private String appName; + private String secret; + + abstract public void setConnectionProperties(P properties); + + abstract public P getConnectionProperties(); +} \ No newline at end of file diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java b/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java new file mode 100644 index 00000000..411abad8 --- /dev/null +++ b/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomain.java @@ -0,0 +1,156 @@ +package org.reactivecommons.async.starter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + + +@Getter +@Setter +public class GenericAsyncPropsDomain, P> extends HashMap { + private Class asyncPropsClass; + private Class

propsClass; + + public GenericAsyncPropsDomain(String defaultAppName, + P defaultProperties, + GenericAsyncPropsDomainProperties configured, + SecretFiller

secretFiller, + Class asyncPropsClass, + Class

propsClass) { + super(configured); + this.propsClass = propsClass; + this.asyncPropsClass = asyncPropsClass; + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + this.computeIfAbsent(DEFAULT_DOMAIN, k -> { + T defaultApp = GenericAsyncPropsDomain.instantiate(asyncPropsClass); + defaultApp.setConnectionProperties(mapper.convertValue(defaultProperties, propsClass)); + return defaultApp; + }); + super.forEach((key, value) -> { // To ensure that each domain has an appName + if (value.getAppName() == null) { + if (defaultAppName == null || defaultAppName.isEmpty()) { + throw new InvalidConfigurationException("defaultAppName does not has value and domain " + key + + " has not set the property appName. please use respective property or " + + " spring.application.name property or withDefaultAppName in builder"); + } + value.setAppName(defaultAppName); + } + if (value.getConnectionProperties() == null) { + if (defaultProperties == null) { + throw new InvalidConfigurationException("Domain " + key + " could not be instantiated because no" + + " properties found, please use withDefaultProperties or define the" + + "default " + key + " domain with properties explicitly"); + } + value.setConnectionProperties(mapper.convertValue(defaultProperties, propsClass)); + } + if (secretFiller != null) { + secretFiller.fillWithSecret(key, value); + } + fillCustoms(value); + }); + } + + protected void fillCustoms(T asyncProps) { + + } + + public T getProps(String domain) { + T props = get(domain); + if (props == null) { + throw new InvalidConfigurationException("Domain " + domain + " id not defined"); + } + return props; + } + + // Static builder strategy + + public static < + T extends GenericAsyncProps

, + P, + X extends GenericAsyncPropsDomainProperties, + R extends GenericAsyncPropsDomain> + AsyncPropsDomainBuilder builder(Class asyncPropsClass, + Class

propsClass, + Class asyncPropsDomainClass, + Constructor returnType) { + return new AsyncPropsDomainBuilder<>(asyncPropsClass, propsClass, asyncPropsDomainClass, returnType); + } + + public static class AsyncPropsDomainBuilder< + T extends GenericAsyncProps

, + P, + X extends GenericAsyncPropsDomainProperties, + R extends GenericAsyncPropsDomain> { + private final Class

propsClass; + private final Class asyncPropsDomainClass; + private final Constructor returnType; + private String defaultAppName; + private final HashMap domains = new HashMap<>(); + private P defaultProperties; + private SecretFiller

secretFiller; + + public AsyncPropsDomainBuilder(Class asynPropsClass, Class

propsClass, Class asyncPropsDomainClass, + Constructor returnType) { + this.propsClass = propsClass; + this.asyncPropsDomainClass = asyncPropsDomainClass; + this.returnType = returnType; + } + + public AsyncPropsDomainBuilder withDefaultProperties(P defaultProperties) { + this.defaultProperties = defaultProperties; + return this; + } + + + public AsyncPropsDomainBuilder withDefaultAppName(String defaultAppName) { + this.defaultAppName = defaultAppName; + return this; + } + + + public AsyncPropsDomainBuilder withSecretFiller(SecretFiller

secretFiller) { + this.secretFiller = secretFiller; + return this; + } + + public AsyncPropsDomainBuilder withDomain(String domain, T props) { + domains.put(domain, props); + return this; + } + + @SneakyThrows + public R build() { + X domainProperties = instantiate(asyncPropsDomainClass, domains); + if (defaultProperties == null) { + defaultProperties = instantiate(propsClass); + } + 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, Map arg) { + return xClass.getDeclaredConstructor(Map.class).newInstance(arg); + } + + public interface SecretFiller

{ + void fillWithSecret(String domain, GenericAsyncProps

props); + } + +} diff --git a/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java b/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java new file mode 100644 index 00000000..f04336c4 --- /dev/null +++ b/starters/shared/src/main/java/org/reactivecommons/async/starter/GenericAsyncPropsDomainProperties.java @@ -0,0 +1,45 @@ +package org.reactivecommons.async.starter; + +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Setter +public class GenericAsyncPropsDomainProperties, P> extends HashMap { + + public GenericAsyncPropsDomainProperties(Map m) { + super(m); + } + + public GenericAsyncPropsDomainProperties() { + } + + public static , + P, + X extends GenericAsyncPropsDomainProperties> AsyncPropsDomainPropertiesBuilder + builder(Class returnType) { + return new AsyncPropsDomainPropertiesBuilder<>(returnType); + } + + public static class AsyncPropsDomainPropertiesBuilder, P, + X extends GenericAsyncPropsDomainProperties> { + private final Map domains = new HashMap<>(); + private final Class returnType; + + public AsyncPropsDomainPropertiesBuilder(Class returnType) { + this.returnType = returnType; + } + + public AsyncPropsDomainPropertiesBuilder withDomain(String domain, T props) { + domains.put(domain, props); + return this; + } + + public X build() { + return returnType.cast(new GenericAsyncPropsDomainProperties<>(domains)); + } + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java b/starters/shared/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java similarity index 72% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java rename to starters/shared/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java index 8db77105..8c076938 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java +++ b/starters/shared/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config.exceptions; +package org.reactivecommons.async.starter.exceptions; public class InvalidConfigurationException extends RuntimeException { public InvalidConfigurationException(String message) { From 28a7b74669248ced91d4aad3ee3a010ca81ed707 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Tue, 27 Aug 2024 14:19:57 +0000 Subject: [PATCH 14/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d0ef2b..7a3a7e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.0.0-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.0-beta) (2024-08-27) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.3-alpha...v5.0.0-beta) + +**Merged pull requests:** + +- chore\(next\): Update starters and extract common reusable code to an aditional module [\#119](https://github.com/reactive-commons/reactive-commons-java/pull/119) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.0.3-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.3-alpha) (2024-08-23) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.2-alpha...v5.0.3-alpha) diff --git a/gradle.properties b/gradle.properties index 23b4da41..40178c8e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.3-alpha +version=5.0.0-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 onlyUpdater=true \ No newline at end of file From d0b4ad0a05a9cb22d92e0a005b920fdacbe9c2c4 Mon Sep 17 00:00:00 2001 From: AndresFelipe11 <53947795+AndresFelipe11@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:13:02 -0500 Subject: [PATCH 15/50] fix(docs): kafka docs (#120) --- docs/docs/reactive-commons/1-getting-started.md | 8 ++++---- docs/docs/reactive-commons/9-configuration-properties.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index 5166d2f1..3deb07a1 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -190,7 +190,7 @@ To enter the Kafka UI, open your browser and go to `http://localhost:8081` ## Spring Boot Application The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to -configure Reactive Commons using RabbitMQ in a Spring Boot environment. +configure Reactive Commons using Kafka in a Spring Boot environment. To build your own application using the Reactive Commons API, you need to include a dependency to Reactive Commons. @@ -210,7 +210,7 @@ dependencies { Also you need to include the name for your app in the `application.properties`, it is important because this value will be used -to name the application queues inside RabbitMQ: +to name the application group-id inside Kafka: ```properties spring.application.name=MyAppName @@ -224,7 +224,7 @@ spring: name: MyAppName ``` -You can set the RabbitMQ connection properties through spring boot with +You can set the Kafka connection properties through spring boot with the [`spring.kafka.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) ```yaml @@ -235,7 +235,7 @@ spring: You can also set it in runtime for example from a secret, so you can create the `KafkaProperties` bean like: -```java title="org.reactivecommons.async.rabbit.config.RabbitProperties" +```java title="org.reactivecommons.async.kafka.config.KafkaProperties" @Configuration public class MyKafkaConfig { diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index bfad7271..2155dacb 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -148,9 +148,9 @@ You can override this settings programmatically through a `AsyncKafkaPropsDomain ```java package sample; -import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncProps; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -163,7 +163,7 @@ public class MyDomainConfig { KafkaProperties propertiesApp = new KafkaProperties(); propertiesApp.setBootstrapServers(List.of("localhost:9092")); - RabbitProperties propertiesAccounts = new RabbitProperties(); + KafkaProperties propertiesAccounts = new KafkaProperties(); propertiesAccounts.setBootstrapServers(List.of("localhost:9093")); return AsyncKafkaPropsDomainProperties.builder() From e3803d9a2429d2c66a2e722fa6accbbe23325315 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:34:15 -0500 Subject: [PATCH 16/50] build(deps): bump webpack from 5.91.0 to 5.94.0 in /docs (#122) Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 43 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 56d21e31..ef6ff564 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -3384,24 +3384,6 @@ "@types/ms": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", - "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -3868,10 +3850,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "peerDependencies": { "acorn": "^8" } @@ -5823,9 +5805,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -14163,20 +14145,19 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", From fb1303351663be9a5d979bc20eda3c97bd3b17e8 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:36:34 -0500 Subject: [PATCH 17/50] chore(next): Fix kafka starter (#121) --- .../annotations/EnableEventListeners.java | 3 +- .../EnableNotificationListener.java | 3 +- .../async/kafka/config/RCKafkaConfig.java | 16 ---------- .../config/RCKafkaEventListenerConfig.java | 2 -- .../config/RCKafkaHandlersConfiguration.java | 32 +++++++++++++++++++ 5 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaHandlersConfiguration.java diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java index b70cee39..366c97e0 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableEventListeners.java @@ -2,6 +2,7 @@ import org.reactivecommons.async.kafka.config.RCKafkaConfig; import org.reactivecommons.async.kafka.config.RCKafkaEventListenerConfig; +import org.reactivecommons.async.kafka.config.RCKafkaHandlersConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -14,7 +15,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import({RCKafkaEventListenerConfig.class, RCKafkaConfig.class}) +@Import({RCKafkaEventListenerConfig.class, RCKafkaHandlersConfiguration.class, RCKafkaConfig.class}) @Configuration public @interface EnableEventListeners { } diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java index a96a0f26..1ae0af73 100644 --- a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/annotations/EnableNotificationListener.java @@ -1,6 +1,7 @@ package org.reactivecommons.async.kafka.annotations; import org.reactivecommons.async.kafka.config.RCKafkaConfig; +import org.reactivecommons.async.kafka.config.RCKafkaHandlersConfiguration; import org.reactivecommons.async.kafka.config.RCKafkaNotificationEventListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -15,7 +16,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import({RCKafkaNotificationEventListenerConfig.class, RCKafkaConfig.class}) +@Import({RCKafkaNotificationEventListenerConfig.class, RCKafkaHandlersConfiguration.class, RCKafkaConfig.class}) @Configuration public @interface EnableNotificationListener { } 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 index 8e023551..950cb497 100644 --- 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 @@ -70,22 +70,6 @@ public ConnectionManager kafkaConnectionManager(AsyncKafkaPropsDomain props, return connectionManager; } - @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; - } - - // Sender @Bean @ConditionalOnMissingBean(DomainEventBus.class) 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 index e3930d40..47ee16c2 100644 --- 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 @@ -1,6 +1,5 @@ package org.reactivecommons.async.kafka.config; -import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; @@ -14,7 +13,6 @@ import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @Configuration -@RequiredArgsConstructor public class RCKafkaEventListenerConfig { @Bean 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 new file mode 100644 index 00000000..7eecd274 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaHandlersConfiguration.java @@ -0,0 +1,32 @@ +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; + } +} From 905beea680ae2b47f2f605c2ed3e1610a4b085a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:39:58 -0500 Subject: [PATCH 18/50] build(deps): bump micromatch from 4.0.7 to 4.0.8 in /docs (#123) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index ef6ff564..97b8f37e 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -10312,9 +10312,9 @@ ] }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" From 2fa73b5c3a8d68b19da19d8c4d6f457c2416529d Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Mon, 2 Sep 2024 15:48:44 +0000 Subject: [PATCH 19/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 18 ++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a3a7e11..1fe71941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [v5.0.1-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.1-beta) (2024-09-02) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.5...v5.0.1-beta) + +**Merged pull requests:** + +- build\(deps\): bump micromatch from 4.0.7 to 4.0.8 in /docs [\#123](https://github.com/reactive-commons/reactive-commons-java/pull/123) ([dependabot[bot]](https://github.com/apps/dependabot)) +- chore\(next\): Fix kafka starter [\#121](https://github.com/reactive-commons/reactive-commons-java/pull/121) ([juancgalvis](https://github.com/juancgalvis)) + +## [v4.1.5](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.5) (2024-09-02) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.0-beta...v4.1.5) + +**Merged pull requests:** + +- build\(deps\): bump webpack from 5.91.0 to 5.94.0 in /docs [\#122](https://github.com/reactive-commons/reactive-commons-java/pull/122) ([dependabot[bot]](https://github.com/apps/dependabot)) +- docs\(kafka\): kafka docs [\#120](https://github.com/reactive-commons/reactive-commons-java/pull/120) ([AndresFelipe11](https://github.com/AndresFelipe11)) + ## [v5.0.0-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.0-beta) (2024-08-27) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.3-alpha...v5.0.0-beta) diff --git a/gradle.properties b/gradle.properties index 40178c8e..80e3741c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.0-beta +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 onlyUpdater=true \ No newline at end of file From 9fd1dcabe44871e274a29f6b3d2398b6df1cef78 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:12:03 -0500 Subject: [PATCH 20/50] chore(next): Create shared starter (#124) * next(shared-starter): Create shared starter classes for any broker type to enable many brokers usage for 5 version * build(sonar): change sonar execution rule * build(test): Add unit tests, update some dependencies * build(sonar): Fix some sonar issues and add unit tests --- .github/workflows/main.yml | 9 +- async/async-commons/async-commons.gradle | 4 +- .../async/commons/config/BrokerConfig.java | 23 +- .../async/kafka/KafkaDirectAsyncGateway.java | 78 ++++++ .../async/kafka/KafkaDomainEventBus.java | 11 + .../kafka/KafkaDirectAsyncGatewayTest.java | 48 ++++ .../async/kafka/KafkaDomainEventBusTest.java | 56 ++++ .../async/rabbit/RabbitDomainEventBus.java | 18 +- .../ApplicationNotificationListener.java | 9 +- .../listeners/GenericMessageListener.java | 3 +- .../rabbit/RabbitDomainEventBusTest.java | 67 +++++ build.gradle | 13 +- .../reactive-commons/1-getting-started.md | 2 +- .../9-configuration-properties.md | 8 +- docs/package-lock.json | 114 ++++---- .../api/domain/DomainEventBus.java | 2 + gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 34 ++- gradlew.bat | 22 +- main.gradle | 45 +++- .../main/java/sample/EDASampleSenderApp.java | 2 +- .../src/main/java/sample/KafkaConfig.java | 4 +- .../src/main/java/sample/ListenerConfig.java | 2 +- .../no-springboot-client.gradle | 3 - settings.gradle | 2 +- .../async-commons-starter.gradle | 17 ++ .../annotations/EnableCommandListeners.java | 10 +- .../annotations/EnableDirectAsyncGateway.java | 8 +- .../annotations/EnableDomainEventBus.java | 6 +- .../annotations/EnableEventListeners.java | 8 +- .../annotations/EnableMessageListeners.java | 30 +++ .../EnableNotificationListener.java | 8 +- .../annotations/EnableQueryListeners.java | 10 +- .../async/starter/broker/BrokerProvider.java | 29 ++ .../starter/broker/BrokerProviderFactory.java | 13 + .../async/starter/broker/DiscardProvider.java | 8 + .../starter/config/ConnectionManager.java | 25 ++ .../async/starter}/config/DomainHandlers.java | 5 +- .../starter/config/ReactiveCommonsConfig.java | 138 ++++++++++ .../health/ReactiveCommonsHealthConfig.java | 20 ++ .../ReactiveCommonsHealthIndicator.java | 38 +++ .../InvalidConfigurationException.java | 0 .../listeners/AbstractListenerConfig.java | 16 ++ .../listeners/CommandsListenerConfig.java | 25 ++ .../listeners/EventsListenerConfig.java | 25 ++ .../NotificationEventsListenerConfig.java | 25 ++ .../listeners/QueriesListenerConfig.java | 25 ++ .../starter/props}/GenericAsyncProps.java | 12 +- .../props}/GenericAsyncPropsDomain.java | 43 +-- .../GenericAsyncPropsDomainProperties.java | 8 +- .../senders/DirectAsyncGatewayConfig.java | 29 ++ .../async/starter/senders/EventBusConfig.java | 23 ++ .../senders/GenericDirectAsyncGateway.java | 83 ++++++ .../senders/GenericDomainEventBus.java | 47 ++++ .../config/ReactiveCommonsConfigTest.java | 30 +++ .../ReactiveCommonsHealthIndicatorTest.java | 74 +++++ .../starter/impl/mybroker/MyBrokerConfig.java | 29 ++ .../listeners/CommandsListenerConfigTest.java | 40 +++ .../listeners/EventsListenerConfigTest.java | 40 +++ .../NotificationEventsListenerConfigTest.java | 40 +++ .../listeners/QueriesListenerConfigTest.java | 40 +++ .../starter/mybroker/MyBrokerProvider.java | 63 +++++ .../mybroker/MyBrokerProviderFactory.java | 27 ++ .../mybroker/MyBrokerSecretFiller.java | 7 + .../AsyncMyBrokerPropsDomainProperties.java | 23 ++ .../mybroker/props/MyBrokerAsyncProps.java | 22 ++ .../props/MyBrokerAsyncPropsDomain.java | 20 ++ .../mybroker/props/MyBrokerConnProps.java | 9 + .../props/GenericAsyncPropsDomainTest.java | 98 +++++++ .../senders/DirectAsyncGatewayConfigTest.java | 53 ++++ .../starter/senders/EventBusConfigTest.java | 45 ++++ .../GenericDirectAsyncGatewayTest.java | 161 +++++++++++ .../senders/GenericDomainEventBusTest.java | 111 ++++++++ .../src/test/resources/application.yaml | 7 + .../async-kafka-starter.gradle | 3 +- .../async/kafka/KafkaBrokerProvider.java | 106 ++++++++ .../kafka/KafkaBrokerProviderFactory.java | 59 ++++ .../async/kafka/KafkaDiscardProvider.java | 34 +++ .../async/kafka/KafkaSetupUtils.java | 86 ++++++ .../async/kafka/config/ConnectionManager.java | 78 ------ .../async/kafka/config/RCKafkaConfig.java | 179 ------------- .../config/RCKafkaEventListenerConfig.java | 49 ---- .../config/RCKafkaHandlersConfiguration.java | 32 --- ...CKafkaNotificationEventListenerConfig.java | 42 --- .../kafka/config/props/AsyncKafkaProps.java | 10 +- .../config/props/AsyncKafkaPropsDomain.java | 5 +- .../AsyncKafkaPropsDomainProperties.java | 4 +- .../config/spring/KafkaPropertiesBase.java | 4 +- .../health/KafkaReactiveHealthIndicator.java | 34 +++ .../starter/impl/kafka/RCKafkaConfig.java | 54 ++++ .../kafka/KafkaBrokerProviderFactoryTest.java | 75 ++++++ .../async/kafka/KafkaBrokerProviderTest.java | 147 ++++++++++ .../async/kafka/KafkaDiscardProviderTest.java | 38 +++ .../KafkaReactiveHealthIndicatorTest.java | 71 +++++ .../starter/impl/rabbit/KafkaConfigTest.java | 44 +++ .../src/test/resources/application.yaml | 8 + .../config/DirectAsyncGatewayConfig.java | 18 +- .../config/EventBusConfig.java | 2 +- .../config/RabbitMqConfig.java | 21 +- .../config/RabbitProperties.java | 4 +- .../async-commons-rabbit-starter.gradle | 2 +- .../async/RabbitEDADirectAsyncGateway.java | 25 -- .../annotations/EnableDomainEventBus.java | 19 -- .../annotations/EnableEventListeners.java | 18 -- .../annotations/EnableMessageListeners.java | 26 -- .../EnableNotificationListener.java | 19 -- .../async/rabbit/RabbitMQBrokerProvider.java | 160 +++++++++++ .../rabbit/RabbitMQBrokerProviderFactory.java | 57 ++++ .../async/rabbit/RabbitMQDiscardProvider.java | 34 +++ .../async/rabbit/RabbitMQSetupUtils.java | 122 +++++++++ .../rabbit/config/CommandListenersConfig.java | 38 --- .../rabbit/config/ConnectionManager.java | 73 ----- .../config/DirectAsyncGatewayConfig.java | 82 ------ .../async/rabbit/config/DomainHandlers.java | 23 -- .../async/rabbit/config/EventBusConfig.java | 31 --- .../rabbit/config/EventListenersConfig.java | 54 ---- .../config/NotificationListenersConfig.java | 41 --- .../rabbit/config/QueryListenerConfig.java | 41 --- .../rabbit/config/RabbitHealthConfig.java | 19 -- .../async/rabbit/config/RabbitMqConfig.java | 253 ------------------ .../async/rabbit/config/RabbitProperties.java | 2 + .../config/RabbitPropertiesAutoConfig.java | 2 + .../async/rabbit/config/props/AsyncProps.java | 11 +- .../rabbit/config/props/AsyncPropsDomain.java | 5 +- .../AsyncRabbitPropsDomainProperties.java | 4 +- .../{ => spring}/RabbitPropertiesBase.java | 10 +- .../DomainRabbitReactiveHealthIndicator.java | 70 ----- .../health/RabbitMQHealthException.java | 7 + .../health/RabbitReactiveHealthIndicator.java | 53 ++++ .../async/rabbit/health/Status.java | 11 - .../starter/impl/rabbit/RabbitMQConfig.java | 63 +++++ .../RabbitMQBrokerProviderFactoryTest.java | 74 +++++ .../rabbit/RabbitMQBrokerProviderTest.java | 192 +++++++++++++ .../rabbit/RabbitMQDiscardProviderTest.java | 37 +++ .../config/CommandListenersConfigTest.java | 67 ----- .../config/EventListenersConfigTest.java | 80 ------ .../NotificationListenersConfigTest.java | 73 ----- .../config/QueryListenerConfigTest.java | 72 ----- .../rabbit/config/RabbitMqConfigTest.java | 67 ----- ...=> RabbitReactiveHealthIndicatorTest.java} | 55 ++-- .../impl/rabbit/RabbitMQConfigTest.java | 42 +++ starters/shared/shared-starter.gradle | 10 - 144 files changed, 3781 insertions(+), 1829 deletions(-) create mode 100644 async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java create mode 100644 async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java create mode 100644 async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java delete mode 100644 samples/async/no-springboot-client/no-springboot-client.gradle create mode 100644 starters/async-commons-starter/async-commons-starter.gradle rename starters/{async-rabbit-starter => async-commons-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java (50%) rename starters/{async-rabbit-starter => async-commons-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java (56%) rename starters/{async-kafka-starter/src/main/java/org/reactivecommons/async/kafka => async-commons-starter/src/main/java/org/reactivecommons/async/impl/config}/annotations/EnableDomainEventBus.java (74%) rename starters/{async-kafka-starter/src/main/java/org/reactivecommons/async/kafka => async-commons-starter/src/main/java/org/reactivecommons/async/impl/config}/annotations/EnableEventListeners.java (55%) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java rename starters/{async-kafka-starter/src/main/java/org/reactivecommons/async/kafka => async-commons-starter/src/main/java/org/reactivecommons/async/impl/config}/annotations/EnableNotificationListener.java (54%) rename starters/{async-rabbit-starter => async-commons-starter}/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java (50%) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java rename starters/{async-kafka-starter/src/main/java/org/reactivecommons/async/kafka => async-commons-starter/src/main/java/org/reactivecommons/async/starter}/config/DomainHandlers.java (72%) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java rename starters/{shared => async-commons-starter}/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java (100%) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java rename starters/{shared/src/main/java/org/reactivecommons/async/starter => async-commons-starter/src/main/java/org/reactivecommons/async/starter/props}/GenericAsyncProps.java (50%) rename starters/{shared/src/main/java/org/reactivecommons/async/starter => async-commons-starter/src/main/java/org/reactivecommons/async/starter/props}/GenericAsyncPropsDomain.java (80%) rename starters/{shared/src/main/java/org/reactivecommons/async/starter => async-commons-starter/src/main/java/org/reactivecommons/async/starter/props}/GenericAsyncPropsDomainProperties.java (82%) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java create mode 100644 starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java create mode 100644 starters/async-commons-starter/src/test/resources/application.yaml create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/ConnectionManager.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaConfig.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaEventListenerConfig.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaHandlersConfiguration.java delete mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/RCKafkaNotificationEventListenerConfig.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java create mode 100644 starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/kafka/RCKafkaConfig.java create mode 100644 starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java create mode 100644 starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java create mode 100644 starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java create mode 100644 starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java create mode 100644 starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/KafkaConfigTest.java create mode 100644 starters/async-kafka-starter/src/test/resources/application.yaml rename starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/{ => standalone}/config/DirectAsyncGatewayConfig.java (77%) rename starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/{ => standalone}/config/EventBusConfig.java (92%) rename starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/{ => standalone}/config/RabbitMqConfig.java (83%) rename starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/{ => standalone}/config/RabbitProperties.java (69%) delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProvider.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java rename starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/{ => spring}/RabbitPropertiesBase.java (98%) delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java delete mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/Status.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfig.java create mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java create mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java create mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderTest.java delete mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java delete mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java delete mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java delete mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java delete mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java rename starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/{DomainRabbitReactiveHealthIndicatorTest.java => RabbitReactiveHealthIndicatorTest.java} (58%) create mode 100644 starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfigTest.java delete mode 100644 starters/shared/shared-starter.gradle 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 943f0cbfa754578e88a3dae77fce6e3dea56edbf..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch literal 43583 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ zQccdaaVK$5bz;*rnyK{_SQYM>;aES6Qs^lj9lEs6_J+%nIiuQC*fN;z8md>r_~Mfl zU%p5Dt_YT>gQqfr@`cR!$NWr~+`CZb%dn;WtzrAOI>P_JtsB76PYe*<%H(y>qx-`Kq!X_; z<{RpAqYhE=L1r*M)gNF3B8r(<%8mo*SR2hu zccLRZwGARt)Hlo1euqTyM>^!HK*!Q2P;4UYrysje@;(<|$&%vQekbn|0Ruu_Io(w4#%p6ld2Yp7tlA`Y$cciThP zKzNGIMPXX%&Ud0uQh!uQZz|FB`4KGD?3!ND?wQt6!n*f4EmCoJUh&b?;B{|lxs#F- z31~HQ`SF4x$&v00@(P+j1pAaj5!s`)b2RDBp*PB=2IB>oBF!*6vwr7Dp%zpAx*dPr zb@Zjq^XjN?O4QcZ*O+8>)|HlrR>oD*?WQl5ri3R#2?*W6iJ>>kH%KnnME&TT@ZzrHS$Q%LC?n|e>V+D+8D zYc4)QddFz7I8#}y#Wj6>4P%34dZH~OUDb?uP%-E zwjXM(?Sg~1!|wI(RVuxbu)-rH+O=igSho_pDCw(c6b=P zKk4ATlB?bj9+HHlh<_!&z0rx13K3ZrAR8W)!@Y}o`?a*JJsD+twZIv`W)@Y?Amu_u zz``@-e2X}27$i(2=9rvIu5uTUOVhzwu%mNazS|lZb&PT;XE2|B&W1>=B58#*!~D&) zfVmJGg8UdP*fx(>Cj^?yS^zH#o-$Q-*$SnK(ZVFkw+er=>N^7!)FtP3y~Xxnu^nzY zikgB>Nj0%;WOltWIob|}%lo?_C7<``a5hEkx&1ku$|)i>Rh6@3h*`slY=9U}(Ql_< zaNG*J8vb&@zpdhAvv`?{=zDedJ23TD&Zg__snRAH4eh~^oawdYi6A3w8<Ozh@Kw)#bdktM^GVb zrG08?0bG?|NG+w^&JvD*7LAbjED{_Zkc`3H!My>0u5Q}m!+6VokMLXxl`Mkd=g&Xx z-a>m*#G3SLlhbKB!)tnzfWOBV;u;ftU}S!NdD5+YtOjLg?X}dl>7m^gOpihrf1;PY zvll&>dIuUGs{Qnd- zwIR3oIrct8Va^Tm0t#(bJD7c$Z7DO9*7NnRZorrSm`b`cxz>OIC;jSE3DO8`hX955ui`s%||YQtt2 z5DNA&pG-V+4oI2s*x^>-$6J?p=I>C|9wZF8z;VjR??Icg?1w2v5Me+FgAeGGa8(3S z4vg*$>zC-WIVZtJ7}o9{D-7d>zCe|z#<9>CFve-OPAYsneTb^JH!Enaza#j}^mXy1 z+ULn^10+rWLF6j2>Ya@@Kq?26>AqK{A_| zQKb*~F1>sE*=d?A?W7N2j?L09_7n+HGi{VY;MoTGr_)G9)ot$p!-UY5zZ2Xtbm=t z@dpPSGwgH=QtIcEulQNI>S-#ifbnO5EWkI;$A|pxJd885oM+ zGZ0_0gDvG8q2xebj+fbCHYfAXuZStH2j~|d^sBAzo46(K8n59+T6rzBwK)^rfPT+B zyIFw)9YC-V^rhtK`!3jrhmW-sTmM+tPH+;nwjL#-SjQPUZ53L@A>y*rt(#M(qsiB2 zx6B)dI}6Wlsw%bJ8h|(lhkJVogQZA&n{?Vgs6gNSXzuZpEyu*xySy8ro07QZ7Vk1!3tJphN_5V7qOiyK8p z#@jcDD8nmtYi1^l8ml;AF<#IPK?!pqf9D4moYk>d99Im}Jtwj6c#+A;f)CQ*f-hZ< z=p_T86jog%!p)D&5g9taSwYi&eP z#JuEK%+NULWus;0w32-SYFku#i}d~+{Pkho&^{;RxzP&0!RCm3-9K6`>KZpnzS6?L z^H^V*s!8<>x8bomvD%rh>Zp3>Db%kyin;qtl+jAv8Oo~1g~mqGAC&Qi_wy|xEt2iz zWAJEfTV%cl2Cs<1L&DLRVVH05EDq`pH7Oh7sR`NNkL%wi}8n>IXcO40hp+J+sC!W?!krJf!GJNE8uj zg-y~Ns-<~D?yqbzVRB}G>0A^f0!^N7l=$m0OdZuqAOQqLc zX?AEGr1Ht+inZ-Qiwnl@Z0qukd__a!C*CKuGdy5#nD7VUBM^6OCpxCa2A(X;e0&V4 zM&WR8+wErQ7UIc6LY~Q9x%Sn*Tn>>P`^t&idaOEnOd(Ufw#>NoR^1QdhJ8s`h^|R_ zXX`c5*O~Xdvh%q;7L!_!ohf$NfEBmCde|#uVZvEo>OfEq%+Ns7&_f$OR9xsihRpBb z+cjk8LyDm@U{YN>+r46?nn{7Gh(;WhFw6GAxtcKD+YWV?uge>;+q#Xx4!GpRkVZYu zzsF}1)7$?%s9g9CH=Zs+B%M_)+~*j3L0&Q9u7!|+T`^O{xE6qvAP?XWv9_MrZKdo& z%IyU)$Q95AB4!#hT!_dA>4e@zjOBD*Y=XjtMm)V|+IXzjuM;(l+8aA5#Kaz_$rR6! zj>#&^DidYD$nUY(D$mH`9eb|dtV0b{S>H6FBfq>t5`;OxA4Nn{J(+XihF(stSche7$es&~N$epi&PDM_N`As;*9D^L==2Q7Z2zD+CiU(|+-kL*VG+&9!Yb3LgPy?A zm7Z&^qRG_JIxK7-FBzZI3Q<;{`DIxtc48k> zc|0dmX;Z=W$+)qE)~`yn6MdoJ4co;%!`ddy+FV538Y)j(vg}5*k(WK)KWZ3WaOG!8 z!syGn=s{H$odtpqFrT#JGM*utN7B((abXnpDM6w56nhw}OY}0TiTG1#f*VFZr+^-g zbP10`$LPq_;PvrA1XXlyx2uM^mrjTzX}w{yuLo-cOClE8MMk47T25G8M!9Z5ypOSV zAJUBGEg5L2fY)ZGJb^E34R2zJ?}Vf>{~gB!8=5Z) z9y$>5c)=;o0HeHHSuE4U)#vG&KF|I%-cF6f$~pdYJWk_dD}iOA>iA$O$+4%@>JU08 zS`ep)$XLPJ+n0_i@PkF#ri6T8?ZeAot$6JIYHm&P6EB=BiaNY|aA$W0I+nz*zkz_z zkEru!tj!QUffq%)8y0y`T&`fuus-1p>=^hnBiBqD^hXrPs`PY9tU3m0np~rISY09> z`P3s=-kt_cYcxWd{de@}TwSqg*xVhp;E9zCsnXo6z z?f&Sv^U7n4`xr=mXle94HzOdN!2kB~4=%)u&N!+2;z6UYKUDqi-s6AZ!haB;@&B`? z_TRX0%@suz^TRdCb?!vNJYPY8L_}&07uySH9%W^Tc&1pia6y1q#?*Drf}GjGbPjBS zbOPcUY#*$3sL2x4v_i*Y=N7E$mR}J%|GUI(>WEr+28+V z%v5{#e!UF*6~G&%;l*q*$V?&r$Pp^sE^i-0$+RH3ERUUdQ0>rAq2(2QAbG}$y{de( z>{qD~GGuOk559Y@%$?N^1ApVL_a704>8OD%8Y%8B;FCt%AoPu8*D1 zLB5X>b}Syz81pn;xnB}%0FnwazlWfUV)Z-~rZg6~b z6!9J$EcE&sEbzcy?CI~=boWA&eeIa%z(7SE^qgVLz??1Vbc1*aRvc%Mri)AJaAG!p z$X!_9Ds;Zz)f+;%s&dRcJt2==P{^j3bf0M=nJd&xwUGlUFn?H=2W(*2I2Gdu zv!gYCwM10aeus)`RIZSrCK=&oKaO_Ry~D1B5!y0R=%!i2*KfXGYX&gNv_u+n9wiR5 z*e$Zjju&ODRW3phN925%S(jL+bCHv6rZtc?!*`1TyYXT6%Ju=|X;6D@lq$8T zW{Y|e39ioPez(pBH%k)HzFITXHvnD6hw^lIoUMA;qAJ^CU?top1fo@s7xT13Fvn1H z6JWa-6+FJF#x>~+A;D~;VDs26>^oH0EI`IYT2iagy23?nyJ==i{g4%HrAf1-*v zK1)~@&(KkwR7TL}L(A@C_S0G;-GMDy=MJn2$FP5s<%wC)4jC5PXoxrQBFZ_k0P{{s@sz+gX`-!=T8rcB(=7vW}^K6oLWMmp(rwDh}b zwaGGd>yEy6fHv%jM$yJXo5oMAQ>c9j`**}F?MCry;T@47@r?&sKHgVe$MCqk#Z_3S z1GZI~nOEN*P~+UaFGnj{{Jo@16`(qVNtbU>O0Hf57-P>x8Jikp=`s8xWs^dAJ9lCQ z)GFm+=OV%AMVqVATtN@|vp61VVAHRn87}%PC^RAzJ%JngmZTasWBAWsoAqBU+8L8u z4A&Pe?fmTm0?mK-BL9t+{y7o(7jm+RpOhL9KnY#E&qu^}B6=K_dB}*VlSEiC9fn)+V=J;OnN)Ta5v66ic1rG+dGAJ1 z1%Zb_+!$=tQ~lxQrzv3x#CPb?CekEkA}0MYSgx$Jdd}q8+R=ma$|&1a#)TQ=l$1tQ z=tL9&_^vJ)Pk}EDO-va`UCT1m#Uty1{v^A3P~83_#v^ozH}6*9mIjIr;t3Uv%@VeW zGL6(CwCUp)Jq%G0bIG%?{_*Y#5IHf*5M@wPo6A{$Um++Co$wLC=J1aoG93&T7Ho}P z=mGEPP7GbvoG!uD$k(H3A$Z))+i{Hy?QHdk>3xSBXR0j!11O^mEe9RHmw!pvzv?Ua~2_l2Yh~_!s1qS`|0~0)YsbHSz8!mG)WiJE| z2f($6TQtt6L_f~ApQYQKSb=`053LgrQq7G@98#igV>y#i==-nEjQ!XNu9 z~;mE+gtj4IDDNQJ~JVk5Ux6&LCSFL!y=>79kE9=V}J7tD==Ga+IW zX)r7>VZ9dY=V&}DR))xUoV!u(Z|%3ciQi_2jl}3=$Agc(`RPb z8kEBpvY>1FGQ9W$n>Cq=DIpski};nE)`p3IUw1Oz0|wxll^)4dq3;CCY@RyJgFgc# zKouFh!`?Xuo{IMz^xi-h=StCis_M7yq$u) z?XHvw*HP0VgR+KR6wI)jEMX|ssqYvSf*_3W8zVTQzD?3>H!#>InzpSO)@SC8q*ii- z%%h}_#0{4JG;Jm`4zg};BPTGkYamx$Xo#O~lBirRY)q=5M45n{GCfV7h9qwyu1NxOMoP4)jjZMxmT|IQQh0U7C$EbnMN<3)Kk?fFHYq$d|ICu>KbY_hO zTZM+uKHe(cIZfEqyzyYSUBZa8;Fcut-GN!HSA9ius`ltNebF46ZX_BbZNU}}ZOm{M2&nANL9@0qvih15(|`S~z}m&h!u4x~(%MAO$jHRWNfuxWF#B)E&g3ghSQ9|> z(MFaLQj)NE0lowyjvg8z0#m6FIuKE9lDO~Glg}nSb7`~^&#(Lw{}GVOS>U)m8bF}x zVjbXljBm34Cs-yM6TVusr+3kYFjr28STT3g056y3cH5Tmge~ASxBj z%|yb>$eF;WgrcOZf569sDZOVwoo%8>XO>XQOX1OyN9I-SQgrm;U;+#3OI(zrWyow3 zk==|{lt2xrQ%FIXOTejR>;wv(Pb8u8}BUpx?yd(Abh6? zsoO3VYWkeLnF43&@*#MQ9-i-d0t*xN-UEyNKeyNMHw|A(k(_6QKO=nKMCxD(W(Yop zsRQ)QeL4X3Lxp^L%wzi2-WVSsf61dqliPUM7srDB?Wm6Lzn0&{*}|IsKQW;02(Y&| zaTKv|`U(pSzuvR6Rduu$wzK_W-Y-7>7s?G$)U}&uK;<>vU}^^ns@Z!p+9?St1s)dG zK%y6xkPyyS1$~&6v{kl?Md6gwM|>mt6Upm>oa8RLD^8T{0?HC!Z>;(Bob7el(DV6x zi`I)$&E&ngwFS@bi4^xFLAn`=fzTC;aimE^!cMI2n@Vo%Ae-ne`RF((&5y6xsjjAZ zVguVoQ?Z9uk$2ON;ersE%PU*xGO@T*;j1BO5#TuZKEf(mB7|g7pcEA=nYJ{s3vlbg zd4-DUlD{*6o%Gc^N!Nptgay>j6E5;3psI+C3Q!1ZIbeCubW%w4pq9)MSDyB{HLm|k zxv-{$$A*pS@csolri$Ge<4VZ}e~78JOL-EVyrbxKra^d{?|NnPp86!q>t<&IP07?Z z^>~IK^k#OEKgRH+LjllZXk7iA>2cfH6+(e&9ku5poo~6y{GC5>(bRK7hwjiurqAiZ zg*DmtgY}v83IjE&AbiWgMyFbaRUPZ{lYiz$U^&Zt2YjG<%m((&_JUbZcfJ22(>bi5 z!J?<7AySj0JZ&<-qXX;mcV!f~>G=sB0KnjWca4}vrtunD^1TrpfeS^4dvFr!65knK zZh`d;*VOkPs4*-9kL>$GP0`(M!j~B;#x?Ba~&s6CopvO86oM?-? zOw#dIRc;6A6T?B`Qp%^<U5 z19x(ywSH$_N+Io!6;e?`tWaM$`=Db!gzx|lQ${DG!zb1Zl&|{kX0y6xvO1o z220r<-oaS^^R2pEyY;=Qllqpmue|5yI~D|iI!IGt@iod{Opz@*ml^w2bNs)p`M(Io z|E;;m*Xpjd9l)4G#KaWfV(t8YUn@A;nK^#xgv=LtnArX|vWQVuw3}B${h+frU2>9^ z!l6)!Uo4`5k`<<;E(ido7M6lKTgWezNLq>U*=uz&s=cc$1%>VrAeOoUtA|T6gO4>UNqsdK=NF*8|~*sl&wI=x9-EGiq*aqV!(VVXA57 zw9*o6Ir8Lj1npUXvlevtn(_+^X5rzdR>#(}4YcB9O50q97%rW2me5_L=%ffYPUSRc z!vv?Kv>dH994Qi>U(a<0KF6NH5b16enCp+mw^Hb3Xs1^tThFpz!3QuN#}KBbww`(h z7GO)1olDqy6?T$()R7y%NYx*B0k_2IBiZ14&8|JPFxeMF{vW>HF-Vi3+ZOI=+qP}n zw(+!WcTd~4ZJX1!ZM&y!+uyt=&i!+~d(V%GjH;-NsEEv6nS1TERt|RHh!0>W4+4pp z1-*EzAM~i`+1f(VEHI8So`S`akPfPTfq*`l{Fz`hS%k#JS0cjT2mS0#QLGf=J?1`he3W*;m4)ce8*WFq1sdP=~$5RlH1EdWm|~dCvKOi4*I_96{^95p#B<(n!d?B z=o`0{t+&OMwKcxiBECznJcfH!fL(z3OvmxP#oWd48|mMjpE||zdiTBdWelj8&Qosv zZFp@&UgXuvJw5y=q6*28AtxZzo-UUpkRW%ne+Ylf!V-0+uQXBW=5S1o#6LXNtY5!I z%Rkz#(S8Pjz*P7bqB6L|M#Er{|QLae-Y{KA>`^} z@lPjeX>90X|34S-7}ZVXe{wEei1<{*e8T-Nbj8JmD4iwcE+Hg_zhkPVm#=@b$;)h6 z<<6y`nPa`f3I6`!28d@kdM{uJOgM%`EvlQ5B2bL)Sl=|y@YB3KeOzz=9cUW3clPAU z^sYc}xf9{4Oj?L5MOlYxR{+>w=vJjvbyO5}ptT(o6dR|ygO$)nVCvNGnq(6;bHlBd zl?w-|plD8spjDF03g5ip;W3Z z><0{BCq!Dw;h5~#1BuQilq*TwEu)qy50@+BE4bX28+7erX{BD4H)N+7U`AVEuREE8 z;X?~fyhF-x_sRfHIj~6f(+^@H)D=ngP;mwJjxhQUbUdzk8f94Ab%59-eRIq?ZKrwD z(BFI=)xrUlgu(b|hAysqK<}8bslmNNeD=#JW*}^~Nrswn^xw*nL@Tx!49bfJecV&KC2G4q5a!NSv)06A_5N3Y?veAz;Gv+@U3R% z)~UA8-0LvVE{}8LVDOHzp~2twReqf}ODIyXMM6=W>kL|OHcx9P%+aJGYi_Om)b!xe zF40Vntn0+VP>o<$AtP&JANjXBn7$}C@{+@3I@cqlwR2MdwGhVPxlTIcRVu@Ho-wO` z_~Or~IMG)A_`6-p)KPS@cT9mu9RGA>dVh5wY$NM9-^c@N=hcNaw4ITjm;iWSP^ZX| z)_XpaI61<+La+U&&%2a z0za$)-wZP@mwSELo#3!PGTt$uy0C(nTT@9NX*r3Ctw6J~7A(m#8fE)0RBd`TdKfAT zCf@$MAxjP`O(u9s@c0Fd@|}UQ6qp)O5Q5DPCeE6mSIh|Rj{$cAVIWsA=xPKVKxdhg zLzPZ`3CS+KIO;T}0Ip!fAUaNU>++ZJZRk@I(h<)RsJUhZ&Ru9*!4Ptn;gX^~4E8W^TSR&~3BAZc#HquXn)OW|TJ`CTahk+{qe`5+ixON^zA9IFd8)kc%*!AiLu z>`SFoZ5bW-%7}xZ>gpJcx_hpF$2l+533{gW{a7ce^B9sIdmLrI0)4yivZ^(Vh@-1q zFT!NQK$Iz^xu%|EOK=n>ug;(7J4OnS$;yWmq>A;hsD_0oAbLYhW^1Vdt9>;(JIYjf zdb+&f&D4@4AS?!*XpH>8egQvSVX`36jMd>$+RgI|pEg))^djhGSo&#lhS~9%NuWfX zDDH;3T*GzRT@5=7ibO>N-6_XPBYxno@mD_3I#rDD?iADxX`! zh*v8^i*JEMzyN#bGEBz7;UYXki*Xr(9xXax(_1qVW=Ml)kSuvK$coq2A(5ZGhs_pF z$*w}FbN6+QDseuB9=fdp_MTs)nQf!2SlROQ!gBJBCXD&@-VurqHj0wm@LWX-TDmS= z71M__vAok|@!qgi#H&H%Vg-((ZfxPAL8AI{x|VV!9)ZE}_l>iWk8UPTGHs*?u7RfP z5MC&=c6X;XlUzrz5q?(!eO@~* zoh2I*%J7dF!!_!vXoSIn5o|wj1#_>K*&CIn{qSaRc&iFVxt*^20ngCL;QonIS>I5^ zMw8HXm>W0PGd*}Ko)f|~dDd%;Wu_RWI_d;&2g6R3S63Uzjd7dn%Svu-OKpx*o|N>F zZg=-~qLb~VRLpv`k zWSdfHh@?dp=s_X`{yxOlxE$4iuyS;Z-x!*E6eqmEm*j2bE@=ZI0YZ5%Yj29!5+J$4h{s($nakA`xgbO8w zi=*r}PWz#lTL_DSAu1?f%-2OjD}NHXp4pXOsCW;DS@BC3h-q4_l`<))8WgzkdXg3! zs1WMt32kS2E#L0p_|x+x**TFV=gn`m9BWlzF{b%6j-odf4{7a4y4Uaef@YaeuPhU8 zHBvRqN^;$Jizy+ z=zW{E5<>2gp$pH{M@S*!sJVQU)b*J5*bX4h>5VJve#Q6ga}cQ&iL#=(u+KroWrxa%8&~p{WEUF0il=db;-$=A;&9M{Rq`ouZ5m%BHT6%st%saGsD6)fQgLN}x@d3q>FC;=f%O3Cyg=Ke@Gh`XW za@RajqOE9UB6eE=zhG%|dYS)IW)&y&Id2n7r)6p_)vlRP7NJL(x4UbhlcFXWT8?K=%s7;z?Vjts?y2+r|uk8Wt(DM*73^W%pAkZa1Jd zNoE)8FvQA>Z`eR5Z@Ig6kS5?0h;`Y&OL2D&xnnAUzQz{YSdh0k zB3exx%A2TyI)M*EM6htrxSlep!Kk(P(VP`$p0G~f$smld6W1r_Z+o?=IB@^weq>5VYsYZZR@` z&XJFxd5{|KPZmVOSxc@^%71C@;z}}WhbF9p!%yLj3j%YOlPL5s>7I3vj25 z@xmf=*z%Wb4;Va6SDk9cv|r*lhZ`(y_*M@>q;wrn)oQx%B(2A$9(74>;$zmQ!4fN; z>XurIk-7@wZys<+7XL@0Fhe-f%*=(weaQEdR9Eh6>Kl-EcI({qoZqyzziGwpg-GM#251sK_ z=3|kitS!j%;fpc@oWn65SEL73^N&t>Ix37xgs= zYG%eQDJc|rqHFia0!_sm7`@lvcv)gfy(+KXA@E{3t1DaZ$DijWAcA)E0@X?2ziJ{v z&KOYZ|DdkM{}t+@{@*6ge}m%xfjIxi%qh`=^2Rwz@w0cCvZ&Tc#UmCDbVwABrON^x zEBK43FO@weA8s7zggCOWhMvGGE`baZ62cC)VHyy!5Zbt%ieH+XN|OLbAFPZWyC6)p z4P3%8sq9HdS3=ih^0OOlqTPbKuzQ?lBEI{w^ReUO{V?@`ARsL|S*%yOS=Z%sF)>-y z(LAQdhgAcuF6LQjRYfdbD1g4o%tV4EiK&ElLB&^VZHbrV1K>tHTO{#XTo>)2UMm`2 z^t4s;vnMQgf-njU-RVBRw0P0-m#d-u`(kq7NL&2T)TjI_@iKuPAK-@oH(J8?%(e!0Ir$yG32@CGUPn5w4)+9@8c&pGx z+K3GKESI4*`tYlmMHt@br;jBWTei&(a=iYslc^c#RU3Q&sYp zSG){)V<(g7+8W!Wxeb5zJb4XE{I|&Y4UrFWr%LHkdQ;~XU zgy^dH-Z3lmY+0G~?DrC_S4@=>0oM8Isw%g(id10gWkoz2Q%7W$bFk@mIzTCcIB(K8 zc<5h&ZzCdT=9n-D>&a8vl+=ZF*`uTvQviG_bLde*k>{^)&0o*b05x$MO3gVLUx`xZ z43j+>!u?XV)Yp@MmG%Y`+COH2?nQcMrQ%k~6#O%PeD_WvFO~Kct za4XoCM_X!c5vhRkIdV=xUB3xI2NNStK*8_Zl!cFjOvp-AY=D;5{uXj}GV{LK1~IE2 z|KffUiBaStRr;10R~K2VVtf{TzM7FaPm;Y(zQjILn+tIPSrJh&EMf6evaBKIvi42-WYU9Vhj~3< zZSM-B;E`g_o8_XTM9IzEL=9Lb^SPhe(f(-`Yh=X6O7+6ALXnTcUFpI>ekl6v)ZQeNCg2 z^H|{SKXHU*%nBQ@I3It0m^h+6tvI@FS=MYS$ZpBaG7j#V@P2ZuYySbp@hA# ze(kc;P4i_-_UDP?%<6>%tTRih6VBgScKU^BV6Aoeg6Uh(W^#J^V$Xo^4#Ekp ztqQVK^g9gKMTHvV7nb64UU7p~!B?>Y0oFH5T7#BSW#YfSB@5PtE~#SCCg3p^o=NkMk$<8- z6PT*yIKGrvne7+y3}_!AC8NNeI?iTY(&nakN>>U-zT0wzZf-RuyZk^X9H-DT_*wk= z;&0}6LsGtfVa1q)CEUPlx#(ED@-?H<1_FrHU#z5^P3lEB|qsxEyn%FOpjx z3S?~gvoXy~L(Q{Jh6*i~=f%9kM1>RGjBzQh_SaIDfSU_9!<>*Pm>l)cJD@wlyxpBV z4Fmhc2q=R_wHCEK69<*wG%}mgD1=FHi4h!98B-*vMu4ZGW~%IrYSLGU{^TuseqVgV zLP<%wirIL`VLyJv9XG_p8w@Q4HzNt-o;U@Au{7%Ji;53!7V8Rv0^Lu^Vf*sL>R(;c zQG_ZuFl)Mh-xEIkGu}?_(HwkB2jS;HdPLSxVU&Jxy9*XRG~^HY(f0g8Q}iqnVmgjI zfd=``2&8GsycjR?M%(zMjn;tn9agcq;&rR!Hp z$B*gzHsQ~aXw8c|a(L^LW(|`yGc!qOnV(ZjU_Q-4z1&0;jG&vAKuNG=F|H?@m5^N@ zq{E!1n;)kNTJ>|Hb2ODt-7U~-MOIFo%9I)_@7fnX+eMMNh>)V$IXesJpBn|uo8f~#aOFytCT zf9&%MCLf8mp4kwHTcojWmM3LU=#|{3L>E}SKwOd?%{HogCZ_Z1BSA}P#O(%H$;z7XyJ^sjGX;j5 zrzp>|Ud;*&VAU3x#f{CKwY7Vc{%TKKqmB@oTHA9;>?!nvMA;8+Jh=cambHz#J18x~ zs!dF>$*AnsQ{{82r5Aw&^7eRCdvcgyxH?*DV5(I$qXh^zS>us*I66_MbL8y4d3ULj z{S(ipo+T3Ag!+5`NU2sc+@*m{_X|&p#O-SAqF&g_n7ObB82~$p%fXA5GLHMC+#qqL zdt`sJC&6C2)=juQ_!NeD>U8lDVpAOkW*khf7MCcs$A(wiIl#B9HM%~GtQ^}yBPjT@ z+E=|A!Z?A(rwzZ;T}o6pOVqHzTr*i;Wrc%&36kc@jXq~+w8kVrs;%=IFdACoLAcCAmhFNpbP8;s`zG|HC2Gv?I~w4ITy=g$`0qMQdkijLSOtX6xW%Z9Nw<;M- zMN`c7=$QxN00DiSjbVt9Mi6-pjv*j(_8PyV-il8Q-&TwBwH1gz1uoxs6~uU}PrgWB zIAE_I-a1EqlIaGQNbcp@iI8W1sm9fBBNOk(k&iLBe%MCo#?xI$%ZmGA?=)M9D=0t7 zc)Q0LnI)kCy{`jCGy9lYX%mUsDWwsY`;jE(;Us@gmWPqjmXL+Hu#^;k%eT>{nMtzj zsV`Iy6leTA8-PndszF;N^X@CJrTw5IIm!GPeu)H2#FQitR{1p;MasQVAG3*+=9FYK zw*k!HT(YQorfQj+1*mCV458(T5=fH`um$gS38hw(OqVMyunQ;rW5aPbF##A3fGH6h z@W)i9Uff?qz`YbK4c}JzQpuxuE3pcQO)%xBRZp{zJ^-*|oryTxJ-rR+MXJ)!f=+pp z10H|DdGd2exhi+hftcYbM0_}C0ZI-2vh+$fU1acsB-YXid7O|=9L!3e@$H*6?G*Zp z%qFB(sgl=FcC=E4CYGp4CN>=M8#5r!RU!u+FJVlH6=gI5xHVD&k;Ta*M28BsxfMV~ zLz+@6TxnfLhF@5=yQo^1&S}cmTN@m!7*c6z;}~*!hNBjuE>NLVl2EwN!F+)0$R1S! zR|lF%n!9fkZ@gPW|x|B={V6x3`=jS*$Pu0+5OWf?wnIy>Y1MbbGSncpKO0qE(qO=ts z!~@&!N`10S593pVQu4FzpOh!tvg}p%zCU(aV5=~K#bKi zHdJ1>tQSrhW%KOky;iW+O_n;`l9~omqM%sdxdLtI`TrJzN6BQz+7xOl*rM>xVI2~# z)7FJ^Dc{DC<%~VS?@WXzuOG$YPLC;>#vUJ^MmtbSL`_yXtNKa$Hk+l-c!aC7gn(Cg ze?YPYZ(2Jw{SF6MiO5(%_pTo7j@&DHNW`|lD`~{iH+_eSTS&OC*2WTT*a`?|9w1dh zh1nh@$a}T#WE5$7Od~NvSEU)T(W$p$s5fe^GpG+7fdJ9=enRT9$wEk+ZaB>G3$KQO zgq?-rZZnIv!p#>Ty~}c*Lb_jxJg$eGM*XwHUwuQ|o^}b3^T6Bxx{!?va8aC@-xK*H ztJBFvFfsSWu89%@b^l3-B~O!CXs)I6Y}y#0C0U0R0WG zybjroj$io0j}3%P7zADXOwHwafT#uu*zfM!oD$6aJx7+WL%t-@6^rD_a_M?S^>c;z zMK580bZXo1f*L$CuMeM4Mp!;P@}b~$cd(s5*q~FP+NHSq;nw3fbWyH)i2)-;gQl{S zZO!T}A}fC}vUdskGSq&{`oxt~0i?0xhr6I47_tBc`fqaSrMOzR4>0H^;A zF)hX1nfHs)%Zb-(YGX;=#2R6C{BG;k=?FfP?9{_uFLri~-~AJ;jw({4MU7e*d)?P@ zXX*GkNY9ItFjhwgAIWq7Y!ksbMzfqpG)IrqKx9q{zu%Mdl+{Dis#p9q`02pr1LG8R z@As?eG!>IoROgS!@J*to<27coFc1zpkh?w=)h9CbYe%^Q!Ui46Y*HO0mr% zEff-*$ndMNw}H2a5@BsGj5oFfd!T(F&0$<{GO!Qdd?McKkorh=5{EIjDTHU`So>8V zBA-fqVLb2;u7UhDV1xMI?y>fe3~4urv3%PX)lDw+HYa;HFkaLqi4c~VtCm&Ca+9C~ zge+67hp#R9`+Euq59WhHX&7~RlXn=--m8$iZ~~1C8cv^2(qO#X0?vl91gzUKBeR1J z^p4!!&7)3#@@X&2aF2-)1Ffcc^F8r|RtdL2X%HgN&XU-KH2SLCbpw?J5xJ*!F-ypZ zMG%AJ!Pr&}`LW?E!K~=(NJxuSVTRCGJ$2a*Ao=uUDSys!OFYu!Vs2IT;xQ6EubLIl z+?+nMGeQQhh~??0!s4iQ#gm3!BpMpnY?04kK375e((Uc7B3RMj;wE?BCoQGu=UlZt!EZ1Q*auI)dj3Jj{Ujgt zW5hd~-HWBLI_3HuO) zNrb^XzPsTIb=*a69wAAA3J6AAZZ1VsYbIG}a`=d6?PjM)3EPaDpW2YP$|GrBX{q*! z$KBHNif)OKMBCFP5>!1d=DK>8u+Upm-{hj5o|Wn$vh1&K!lVfDB&47lw$tJ?d5|=B z^(_9=(1T3Fte)z^>|3**n}mIX;mMN5v2F#l(q*CvU{Ga`@VMp#%rQkDBy7kYbmb-q z<5!4iuB#Q_lLZ8}h|hPODI^U6`gzLJre9u3k3c#%86IKI*^H-@I48Bi*@avYm4v!n0+v zWu{M{&F8#p9cx+gF0yTB_<2QUrjMPo9*7^-uP#~gGW~y3nfPAoV%amgr>PSyVAd@l)}8#X zR5zV6t*uKJZL}?NYvPVK6J0v4iVpwiN|>+t3aYiZSp;m0!(1`bHO}TEtWR1tY%BPB z(W!0DmXbZAsT$iC13p4f>u*ZAy@JoLAkJhzFf1#4;#1deO8#8d&89}en&z!W&A3++^1(;>0SB1*54d@y&9Pn;^IAf3GiXbfT`_>{R+Xv; zQvgL>+0#8-laO!j#-WB~(I>l0NCMt_;@Gp_f0#^c)t?&#Xh1-7RR0@zPyBz!U#0Av zT?}n({(p?p7!4S2ZBw)#KdCG)uPnZe+U|0{BW!m)9 zi_9$F?m<`2!`JNFv+w8MK_K)qJ^aO@7-Ig>cM4-r0bi=>?B_2mFNJ}aE3<+QCzRr*NA!QjHw# z`1OsvcoD0?%jq{*7b!l|L1+Tw0TTAM4XMq7*ntc-Ived>Sj_ZtS|uVdpfg1_I9knY z2{GM_j5sDC7(W&}#s{jqbybqJWyn?{PW*&cQIU|*v8YGOKKlGl@?c#TCnmnAkAzV- zmK={|1G90zz=YUvC}+fMqts0d4vgA%t6Jhjv?d;(Z}(Ep8fTZfHA9``fdUHkA+z3+ zhh{ohP%Bj?T~{i0sYCQ}uC#5BwN`skI7`|c%kqkyWIQ;!ysvA8H`b-t()n6>GJj6xlYDu~8qX{AFo$Cm3d|XFL=4uvc?Keb zzb0ZmMoXca6Mob>JqkNuoP>B2Z>D`Q(TvrG6m`j}-1rGP!g|qoL=$FVQYxJQjFn33lODt3Wb1j8VR zlR++vIT6^DtYxAv_hxupbLLN3e0%A%a+hWTKDV3!Fjr^cWJ{scsAdfhpI)`Bms^M6 zQG$waKgFr=c|p9Piug=fcJvZ1ThMnNhQvBAg-8~b1?6wL*WyqXhtj^g(Ke}mEfZVM zJuLNTUVh#WsE*a6uqiz`b#9ZYg3+2%=C(6AvZGc=u&<6??!slB1a9K)=VL zY9EL^mfyKnD zSJyYBc_>G;5RRnrNgzJz#Rkn3S1`mZgO`(r5;Hw6MveN(URf_XS-r58Cn80K)ArH4 z#Rrd~LG1W&@ttw85cjp8xV&>$b%nSXH_*W}7Ch2pg$$c0BdEo-HWRTZcxngIBJad> z;C>b{jIXjb_9Jis?NZJsdm^EG}e*pR&DAy0EaSGi3XWTa(>C%tz1n$u?5Fb z1qtl?;_yjYo)(gB^iQq?=jusF%kywm?CJP~zEHi0NbZ);$(H$w(Hy@{i>$wcVRD_X|w-~(0Z9BJyh zhNh;+eQ9BEIs;tPz%jSVnfCP!3L&9YtEP;svoj_bNzeGSQIAjd zBss@A;)R^WAu-37RQrM%{DfBNRx>v!G31Z}8-El9IOJlb_MSoMu2}GDYycNaf>uny z+8xykD-7ONCM!APry_Lw6-yT>5!tR}W;W`C)1>pxSs5o1z#j7%m=&=7O4hz+Lsqm` z*>{+xsabZPr&X=}G@obTb{nPTkccJX8w3CG7X+1+t{JcMabv~UNv+G?txRqXib~c^Mo}`q{$`;EBNJ;#F*{gvS12kV?AZ%O0SFB$^ zn+}!HbmEj}w{Vq(G)OGAzH}R~kS^;(-s&=ectz8vN!_)Yl$$U@HNTI-pV`LSj7Opu zTZ5zZ)-S_{GcEQPIQXLQ#oMS`HPu{`SQiAZ)m1at*Hy%3xma|>o`h%E%8BEbi9p0r zVjcsh<{NBKQ4eKlXU|}@XJ#@uQw*$4BxKn6#W~I4T<^f99~(=}a`&3(ur8R9t+|AQ zWkQx7l}wa48-jO@ft2h+7qn%SJtL%~890FG0s5g*kNbL3I&@brh&f6)TlM`K^(bhr zJWM6N6x3flOw$@|C@kPi7yP&SP?bzP-E|HSXQXG>7gk|R9BTj`e=4de9C6+H7H7n# z#GJeVs1mtHhLDmVO?LkYRQc`DVOJ_vdl8VUihO-j#t=0T3%Fc1f9F73ufJz*adn*p zc%&vi(4NqHu^R>sAT_0EDjVR8bc%wTz#$;%NU-kbDyL_dg0%TFafZwZ?5KZpcuaO54Z9hX zD$u>q!-9`U6-D`E#`W~fIfiIF5_m6{fvM)b1NG3xf4Auw;Go~Fu7cth#DlUn{@~yu z=B;RT*dp?bO}o%4x7k9v{r=Y@^YQ^UUm(Qmliw8brO^=NP+UOohLYiaEB3^DB56&V zK?4jV61B|1Uj_5fBKW;8LdwOFZKWp)g{B%7g1~DgO&N& z#lisxf?R~Z@?3E$Mms$$JK8oe@X`5m98V*aV6Ua}8Xs2#A!{x?IP|N(%nxsH?^c{& z@vY&R1QmQs83BW28qAmJfS7MYi=h(YK??@EhjL-t*5W!p z^gYX!Q6-vBqcv~ruw@oMaU&qp0Fb(dbVzm5xJN%0o_^@fWq$oa3X?9s%+b)x4w-q5Koe(@j6Ez7V@~NRFvd zfBH~)U5!ix3isg`6be__wBJp=1@yfsCMw1C@y+9WYD9_C%{Q~7^0AF2KFryfLlUP# zwrtJEcH)jm48!6tUcxiurAMaiD04C&tPe6DI0#aoqz#Bt0_7_*X*TsF7u*zv(iEfA z;$@?XVu~oX#1YXtceQL{dSneL&*nDug^OW$DSLF0M1Im|sSX8R26&)<0Fbh^*l6!5wfSu8MpMoh=2l z^^0Sr$UpZp*9oqa23fcCfm7`ya2<4wzJ`Axt7e4jJrRFVf?nY~2&tRL* zd;6_njcz01c>$IvN=?K}9ie%Z(BO@JG2J}fT#BJQ+f5LFSgup7i!xWRKw6)iITjZU z%l6hPZia>R!`aZjwCp}I zg)%20;}f+&@t;(%5;RHL>K_&7MH^S+7<|(SZH!u zznW|jz$uA`P9@ZWtJgv$EFp>)K&Gt+4C6#*khZQXS*S~6N%JDT$r`aJDs9|uXWdbg zBwho$phWx}x!qy8&}6y5Vr$G{yGSE*r$^r{}pw zVTZKvikRZ`J_IJrjc=X1uw?estdwm&bEahku&D04HD+0Bm~q#YGS6gp!KLf$A{%Qd z&&yX@Hp>~(wU{|(#U&Bf92+1i&Q*-S+=y=3pSZy$#8Uc$#7oiJUuO{cE6=tsPhwPe| zxQpK>`Dbka`V)$}e6_OXKLB%i76~4N*zA?X+PrhH<&)}prET;kel24kW%+9))G^JI zsq7L{P}^#QsZViX%KgxBvEugr>ZmFqe^oAg?{EI=&_O#e)F3V#rc z8$4}0Zr19qd3tE4#$3_f=Bbx9oV6VO!d3(R===i-7p=Vj`520w0D3W6lQfY48}!D* z&)lZMG;~er2qBoI2gsX+Ts-hnpS~NYRDtPd^FPzn!^&yxRy#CSz(b&E*tL|jIkq|l zf%>)7Dtu>jCf`-7R#*GhGn4FkYf;B$+9IxmqH|lf6$4irg{0ept__%)V*R_OK=T06 zyT_m-o@Kp6U{l5h>W1hGq*X#8*y@<;vsOFqEjTQXFEotR+{3}ODDnj;o0@!bB5x=N z394FojuGOtVKBlVRLtHp%EJv_G5q=AgF)SKyRN5=cGBjDWv4LDn$IL`*=~J7u&Dy5 zrMc83y+w^F&{?X(KOOAl-sWZDb{9X9#jrQtmrEXD?;h-}SYT7yM(X_6qksM=K_a;Z z3u0qT0TtaNvDER_8x*rxXw&C^|h{P1qxK|@pS7vdlZ#P z7PdB7MmC2}%sdzAxt>;WM1s0??`1983O4nFK|hVAbHcZ3x{PzytQLkCVk7hA!Lo` zEJH?4qw|}WH{dc4z%aB=0XqsFW?^p=X}4xnCJXK%c#ItOSjdSO`UXJyuc8bh^Cf}8 z@Ht|vXd^6{Fgai8*tmyRGmD_s_nv~r^Fy7j`Bu`6=G)5H$i7Q7lvQnmea&TGvJp9a|qOrUymZ$6G|Ly z#zOCg++$3iB$!6!>215A4!iryregKuUT344X)jQb3|9qY>c0LO{6Vby05n~VFzd?q zgGZv&FGlkiH*`fTurp>B8v&nSxNz)=5IF$=@rgND4d`!AaaX;_lK~)-U8la_Wa8i?NJC@BURO*sUW)E9oyv3RG^YGfN%BmxzjlT)bp*$<| zX3tt?EAy<&K+bhIuMs-g#=d1}N_?isY)6Ay$mDOKRh z4v1asEGWoAp=srraLW^h&_Uw|6O+r;wns=uwYm=JN4Q!quD8SQRSeEcGh|Eb5Jg8m zOT}u;N|x@aq)=&;wufCc^#)5U^VcZw;d_wwaoh9$p@Xrc{DD6GZUqZ ziC6OT^zSq@-lhbgR8B+e;7_Giv;DK5gn^$bs<6~SUadiosfewWDJu`XsBfOd1|p=q zE>m=zF}!lObA%ePey~gqU8S6h-^J2Y?>7)L2+%8kV}Gp=h`Xm_}rlm)SyUS=`=S7msKu zC|T!gPiI1rWGb1z$Md?0YJQ;%>uPLOXf1Z>N~`~JHJ!^@D5kSXQ4ugnFZ>^`zH8CAiZmp z6Ms|#2gcGsQ{{u7+Nb9sA?U>(0e$5V1|WVwY`Kn)rsnnZ4=1u=7u!4WexZD^IQ1Jk zfF#NLe>W$3m&C^ULjdw+5|)-BSHwpegdyt9NYC{3@QtMfd8GrIWDu`gd0nv-3LpGCh@wgBaG z176tikL!_NXM+Bv#7q^cyn9$XSeZR6#!B4JE@GVH zoobHZN_*RF#@_SVYKkQ_igme-Y5U}cV(hkR#k1c{bQNMji zU7aE`?dHyx=1`kOYZo_8U7?3-7vHOp`Qe%Z*i+FX!s?6huNp0iCEW-Z7E&jRWmUW_ z67j>)Ew!yq)hhG4o?^z}HWH-e=es#xJUhDRc4B51M4~E-l5VZ!&zQq`gWe`?}#b~7w1LH4Xa-UCT5LXkXQWheBa2YJYbyQ zl1pXR%b(KCXMO0OsXgl0P0Og<{(@&z1aokU-Pq`eQq*JYgt8xdFQ6S z6Z3IFSua8W&M#`~*L#r>Jfd6*BzJ?JFdBR#bDv$_0N!_5vnmo@!>vULcDm`MFU823 zpG9pqjqz^FE5zMDoGqhs5OMmC{Y3iVcl>F}5Rs24Y5B^mYQ;1T&ks@pIApHOdrzXF z-SdX}Hf{X;TaSxG_T$0~#RhqKISGKNK47}0*x&nRIPtmdwxc&QT3$8&!3fWu1eZ_P zJveQj^hJL#Sn!*4k`3}(d(aasl&7G0j0-*_2xtAnoX1@9+h zO#c>YQg60Z;o{Bi=3i7S`Ic+ZE>K{(u|#)9y}q*j8uKQ1^>+(BI}m%1v3$=4ojGBc zm+o1*!T&b}-lVvZqIUBc8V}QyFEgm#oyIuC{8WqUNV{Toz`oxhYpP!_p2oHHh5P@iB*NVo~2=GQm+8Yrkm2Xjc_VyHg1c0>+o~@>*Qzo zHVBJS>$$}$_4EniTI;b1WShX<5-p#TPB&!;lP!lBVBbLOOxh6FuYloD%m;n{r|;MU3!q4AVkua~fieeWu2 zQAQ$ue(IklX6+V;F1vCu-&V?I3d42FgWgsb_e^29ol}HYft?{SLf>DrmOp9o!t>I^ zY7fBCk+E8n_|apgM|-;^=#B?6RnFKlN`oR)`e$+;D=yO-(U^jV;rft^G_zl`n7qnM zL z*-Y4Phq+ZI1$j$F-f;`CD#|`-T~OM5Q>x}a>B~Gb3-+9i>Lfr|Ca6S^8g*{*?_5!x zH_N!SoRP=gX1?)q%>QTY!r77e2j9W(I!uAz{T`NdNmPBBUzi2{`XMB^zJGGwFWeA9 z{fk33#*9SO0)DjROug+(M)I-pKA!CX;IY(#gE!UxXVsa)X!UftIN98{pt#4MJHOhY zM$_l}-TJlxY?LS6Nuz1T<44m<4i^8k@D$zuCPrkmz@sdv+{ciyFJG2Zwy&%c7;atIeTdh!a(R^QXnu1Oq1b42*OQFWnyQ zWeQrdvP|w_idy53Wa<{QH^lFmEd+VlJkyiC>6B#s)F;w-{c;aKIm;Kp50HnA-o3lY z9B~F$gJ@yYE#g#X&3ADx&tO+P_@mnQTz9gv30_sTsaGXkfNYXY{$(>*PEN3QL>I!k zp)KibPhrfX3%Z$H6SY`rXGYS~143wZrG2;=FLj50+VM6soI~up_>fU(2Wl@{BRsMi zO%sL3x?2l1cXTF)k&moNsHfQrQ+wu(gBt{sk#CU=UhrvJIncy@tJX5klLjgMn>~h= zg|FR&;@eh|C7`>s_9c~0-{IAPV){l|Ts`i=)AW;d9&KPc3fMeoTS%8@V~D8*h;&(^>yjT84MM}=%#LS7shLAuuj(0VAYoozhWjq z4LEr?wUe2^WGwdTIgWBkDUJa>YP@5d9^Rs$kCXmMRxuF*YMVrn?0NFyPl}>`&dqZb z<5eqR=ZG3>n2{6v6BvJ`YBZeeTtB88TAY(x0a58EWyuf>+^|x8Qa6wA|1Nb_p|nA zWWa}|z8a)--Wj`LqyFk_a3gN2>5{Rl_wbW?#by7&i*^hRknK%jwIH6=dQ8*-_{*x0j^DUfMX0`|K@6C<|1cgZ~D(e5vBFFm;HTZF(!vT8=T$K+|F)x3kqzBV4-=p1V(lzi(s7jdu0>LD#N=$Lk#3HkG!a zIF<7>%B7sRNzJ66KrFV76J<2bdYhxll0y2^_rdG=I%AgW4~)1Nvz=$1UkE^J%BxLo z+lUci`UcU062os*=`-j4IfSQA{w@y|3}Vk?i;&SSdh8n+$iHA#%ERL{;EpXl6u&8@ zzg}?hkEOUOJt?ZL=pWZFJ19mI1@P=$U5*Im1e_8Z${JsM>Ov?nh8Z zP5QvI!{Jy@&BP48%P2{Jr_VgzW;P@7)M9n|lDT|Ep#}7C$&ud&6>C^5ZiwKIg2McPU(4jhM!BD@@L(Gd*Nu$ji(ljZ<{FIeW_1Mmf;76{LU z-ywN~=uNN)Xi6$<12A9y)K%X|(W0p|&>>4OXB?IiYr||WKDOJPxiSe01NSV-h24^L z_>m$;|C+q!Mj**-qQ$L-*++en(g|hw;M!^%_h-iDjFHLo-n3JpB;p?+o2;`*jpvJU zLY^lt)Un4joij^^)O(CKs@7E%*!w>!HA4Q?0}oBJ7Nr8NQ7QmY^4~jvf0-`%waOLn zdNjAPaC0_7c|RVhw)+71NWjRi!y>C+Bl;Z`NiL^zn2*0kmj5gyhCLCxts*cWCdRI| zjsd=sT5BVJc^$GxP~YF$-U{-?kW6r@^vHXB%{CqYzU@1>dzf#3SYedJG-Rm6^RB7s zGM5PR(yKPKR)>?~vpUIeTP7A1sc8-knnJk*9)3t^e%izbdm>Y=W{$wm(cy1RB-19i za#828DMBY+ps#7Y8^6t)=Ea@%Nkt)O6JCx|ybC;Ap}Z@Zw~*}3P>MZLPb4Enxz9Wf zssobT^(R@KuShj8>@!1M7tm|2%-pYYDxz-5`rCbaTCG5{;Uxm z*g=+H1X8{NUvFGzz~wXa%Eo};I;~`37*WrRU&K0dPSB$yk(Z*@K&+mFal^?c zurbqB-+|Kb5|sznT;?Pj!+kgFY1#Dr;_%A(GIQC{3ct|{*Bji%FNa6c-thbpBkA;U zURV!Dr&X{0J}iht#-Qp2=xzuh(fM>zRoiGrYl5ttw2#r34gC41CCOC31m~^UPTK@s z6;A@)7O7_%C)>bnAXerYuAHdE93>j2N}H${zEc6&SbZ|-fiG*-qtGuy-qDelH(|u$ zorf8_T6Zqe#Ub!+e3oSyrskt_HyW_^5lrWt#30l)tHk|j$@YyEkXUOV;6B51L;M@=NIWZXU;GrAa(LGxO%|im%7F<-6N;en0Cr zLH>l*y?pMwt`1*cH~LdBPFY_l;~`N!Clyfr;7w<^X;&(ZiVdF1S5e(+Q%60zgh)s4 zn2yj$+mE=miVERP(g8}G4<85^-5f@qxh2ec?n+$A_`?qN=iyT1?U@t?V6DM~BIlBB z>u~eXm-aE>R0sQy!-I4xtCNi!!qh?R1!kKf6BoH2GG{L4%PAz0{Sh6xpuyI%*~u)s z%rLuFl)uQUCBQAtMyN;%)zFMx4loh7uTfKeB2Xif`lN?2gq6NhWhfz0u5WP9J>=V2 zo{mLtSy&BA!mSzs&CrKWq^y40JF5a&GSXIi2= z{EYb59J4}VwikL4P=>+mc6{($FNE@e=VUwG+KV21;<@lrN`mnz5jYGASyvz7BOG_6(p^eTxD-4O#lROgon;R35=|nj#eHIfJBYPWG>H>`dHKCDZ3`R{-?HO0mE~(5_WYcFmp8sU?wr*UkAQiNDGc6T zA%}GOLXlOWqL?WwfHO8MB#8M8*~Y*gz;1rWWoVSXP&IbKxbQ8+s%4Jnt?kDsq7btI zCDr0PZ)b;B%!lu&CT#RJzm{l{2fq|BcY85`w~3LSK<><@(2EdzFLt9Y_`;WXL6x`0 zDoQ?=?I@Hbr;*VVll1Gmd8*%tiXggMK81a+T(5Gx6;eNb8=uYn z5BG-0g>pP21NPn>$ntBh>`*})Fl|38oC^9Qz>~MAazH%3Q~Qb!ALMf$srexgPZ2@&c~+hxRi1;}+)-06)!#Mq<6GhP z-Q?qmgo${aFBApb5p}$1OJKTClfi8%PpnczyVKkoHw7Ml9e7ikrF0d~UB}i3vizos zXW4DN$SiEV9{faLt5bHy2a>33K%7Td-n5C*N;f&ZqAg#2hIqEb(y<&f4u5BWJ>2^4 z414GosL=Aom#m&=x_v<0-fp1r%oVJ{T-(xnomNJ(Dryv zh?vj+%=II_nV+@NR+(!fZZVM&(W6{6%9cm+o+Z6}KqzLw{(>E86uA1`_K$HqINlb1 zKelh3-jr2I9V?ych`{hta9wQ2c9=MM`2cC{m6^MhlL2{DLv7C^j z$xXBCnDl_;l|bPGMX@*tV)B!c|4oZyftUlP*?$YU9C_eAsuVHJ58?)zpbr30P*C`T z7y#ao`uE-SOG(Pi+`$=e^mle~)pRrdwL5)N;o{gpW21of(QE#U6w%*C~`v-z0QqBML!!5EeYA5IQB0 z^l01c;L6E(iytN!LhL}wfwP7W9PNAkb+)Cst?qg#$n;z41O4&v+8-zPs+XNb-q zIeeBCh#ivnFLUCwfS;p{LC0O7tm+Sf9Jn)~b%uwP{%69;QC)Ok0t%*a5M+=;y8j=v z#!*pp$9@!x;UMIs4~hP#pnfVc!%-D<+wsG@R2+J&%73lK|2G!EQC)O05TCV=&3g)C!lT=czLpZ@Sa%TYuoE?v8T8`V;e$#Zf2_Nj6nvBgh1)2 GZ~q4|mN%#X literal 61574 zcmb6AV{~QRwml9f72CFLyJFk6ZKq;e729@pY}>YNR8p1vbMJH7ubt# zZR`2@zJD1Ad^Oa6Hk1{VlN1wGR-u;_dyt)+kddaNpM#U8qn@6eX;fldWZ6BspQIa= zoRXcQk)#ENJ`XiXJuK3q0$`Ap92QXrW00Yv7NOrc-8ljOOOIcj{J&cR{W`aIGXJ-` z`ez%Mf7qBi8JgIb{-35Oe>Zh^GIVe-b^5nULQhxRDZa)^4+98@`hUJe{J%R>|LYHA z4K3~Hjcp8_owGF{d~lZVKJ;kc48^OQ+`_2migWY?JqgW&))70RgSB6KY9+&wm<*8 z_{<;(c;5H|u}3{Y>y_<0Z59a)MIGK7wRMX0Nvo>feeJs+U?bt-++E8bu7 zh#_cwz0(4#RaT@xy14c7d<92q-Dd}Dt<*RS+$r0a^=LGCM{ny?rMFjhgxIG4>Hc~r zC$L?-FW0FZ((8@dsowXlQq}ja%DM{z&0kia*w7B*PQ`gLvPGS7M}$T&EPl8mew3In z0U$u}+bk?Vei{E$6dAYI8Tsze6A5wah?d(+fyP_5t4ytRXNktK&*JB!hRl07G62m_ zAt1nj(37{1p~L|m(Bsz3vE*usD`78QTgYIk zQ6BF14KLzsJTCqx&E!h>XP4)bya|{*G7&T$^hR0(bOWjUs2p0uw7xEjbz1FNSBCDb@^NIA z$qaq^0it^(#pFEmuGVS4&-r4(7HLmtT%_~Xhr-k8yp0`$N|y>#$Ao#zibzGi*UKzi zhaV#@e1{2@1Vn2iq}4J{1-ox;7K(-;Sk{3G2_EtV-D<)^Pk-G<6-vP{W}Yd>GLL zuOVrmN@KlD4f5sVMTs7c{ATcIGrv4@2umVI$r!xI8a?GN(R;?32n0NS(g@B8S00-=zzLn z%^Agl9eV(q&8UrK^~&$}{S(6-nEXnI8%|hoQ47P?I0Kd=woZ-pH==;jEg+QOfMSq~ zOu>&DkHsc{?o&M5`jyJBWbfoPBv9Y#70qvoHbZXOj*qRM(CQV=uX5KN+b>SQf-~a8 ziZg}@&XHHXkAUqr)Q{y`jNd7`1F8nm6}n}+_She>KO`VNlnu(&??!(i#$mKOpWpi1 z#WfWxi3L)bNRodhPM~~?!5{TrrBY_+nD?CIUupkwAPGz-P;QYc-DcUoCe`w(7)}|S zRvN)9ru8b)MoullmASwsgKQo1U6nsVAvo8iKnbaWydto4y?#-|kP^%e6m@L`88KyDrLH`=EDx*6>?r5~7Iv~I zr__%SximG(izLKSnbTlXa-ksH@R6rvBrBavt4)>o3$dgztLt4W=!3=O(*w7I+pHY2(P0QbTma+g#dXoD7N#?FaXNQ^I0*;jzvjM}%=+km`YtC%O#Alm| zqgORKSqk!#^~6whtLQASqiJ7*nq?38OJ3$u=Tp%Y`x^eYJtOqTzVkJ60b2t>TzdQ{I}!lEBxm}JSy7sy8DpDb zIqdT%PKf&Zy--T^c-;%mbDCxLrMWTVLW}c=DP2>Td74)-mLl|70)8hU??(2)I@Zyo z2i`q5oyA!!(2xV~gahuKl&L(@_3SP012#x(7P!1}6vNFFK5f*A1xF({JwxSFwA|TM z&1z}!*mZKcUA-v4QzLz&5wS$7=5{M@RAlx@RkJaA4nWVqsuuaW(eDh^LNPPkmM~Al zwxCe@*-^4!ky#iNv2NIIU$CS+UW%ziW0q@6HN3{eCYOUe;2P)C*M`Bt{~-mC%T3%# zEaf)lATO1;uF33x>Hr~YD0Ju*Syi!Jz+x3myVvU^-O>C*lFCKS&=Tuz@>&o?68aF& zBv<^ziPywPu#;WSlTkzdZ9`GWe7D8h<1-v0M*R@oYgS5jlPbgHcx)n2*+!+VcGlYh?;9Ngkg% z=MPD+`pXryN1T|%I7c?ZPLb3bqWr7 zU4bfG1y+?!bw)5Iq#8IqWN@G=Ru%Thxf)#=yL>^wZXSCC8we@>$hu=yrU;2=7>h;5 zvj_pYgKg2lKvNggl1ALnsz2IlcvL;q79buN5T3IhXuJvy@^crqWpB-5NOm{7UVfxmPJ>`?;Tn@qHzF+W!5W{8Z&ZAnDOquw6r4$bv*jM#5lc%3v|c~^ zdqo4LuxzkKhK4Q+JTK8tR_|i6O(x#N2N0Fy5)!_trK&cn9odQu#Vlh1K~7q|rE z61#!ZPZ+G&Y7hqmY;`{XeDbQexC2@oFWY)Nzg@lL3GeEVRxWQlx@0?Zt`PcP0iq@6 zLgc)p&s$;*K_;q0L(mQ8mKqOJSrq$aQYO-Hbssf3P=wC6CvTVHudzJH-Jgm&foBSy zx0=qu$w477lIHk);XhaUR!R-tQOZ;tjLXFH6;%0)8^IAc*MO>Q;J={We(0OHaogG0 zE_C@bXic&m?F7slFAB~x|n#>a^@u8lu;=!sqE*?vq zu4`(x!Jb4F#&3+jQ|ygldPjyYn#uCjNWR)%M3(L!?3C`miKT;~iv_)dll>Q6b+I&c zrlB04k&>mSYLR7-k{Od+lARt~3}Bv!LWY4>igJl!L5@;V21H6dNHIGr+qV551e@yL z`*SdKGPE^yF?FJ|`#L)RQ?LJ;8+={+|Cl<$*ZF@j^?$H%V;jqVqt#2B0yVr}Nry5R z5D?S9n+qB_yEqvdy9nFc+8WxK$XME$3ftSceLb+L(_id5MMc*hSrC;E1SaZYow%jh zPgo#1PKjE+1QB`Of|aNmX?}3TP;y6~0iN}TKi3b+yvGk;)X&i3mTnf9M zuv3qvhErosfZ%Pb-Q>|BEm5(j-RV6Zf^$icM=sC-5^6MnAvcE9xzH@FwnDeG0YU{J zi~Fq?=bi0;Ir=hfOJu8PxC)qjYW~cv^+74Hs#GmU%Cw6?3LUUHh|Yab`spoqh8F@_ zm4bCyiXPx-Cp4!JpI~w!ShPfJOXsy>f*|$@P8L8(oeh#~w z-2a4IOeckn6}_TQ+rgl_gLArS3|Ml(i<`*Lqv6rWh$(Z5ycTYD#Z*&-5mpa}a_zHt z6E`Ty-^L9RK-M*mN5AasoBhc|XWZ7=YRQSvG)3$v zgr&U_X`Ny0)IOZtX}e$wNUzTpD%iF7Rgf?nWoG2J@PsS-qK4OD!kJ?UfO+1|F*|Bo z1KU`qDA^;$0*4mUJ#{EPOm7)t#EdX=Yx1R2T&xlzzThfRC7eq@pX&%MO&2AZVO%zw zS;A{HtJiL=rfXDigS=NcWL-s>Rbv|=)7eDoOVnVI>DI_8x>{E>msC$kXsS}z?R6*x zi(yO`$WN)_F1$=18cbA^5|f`pZA+9DG_Zu8uW?rA9IxUXx^QCAp3Gk1MSdq zBZv;_$W>*-zLL)F>Vn`}ti1k!%6{Q=g!g1J*`KONL#)M{ZC*%QzsNRaL|uJcGB7jD zTbUe%T(_x`UtlM!Ntp&-qu!v|mPZGcJw$mdnanY3Uo>5{oiFOjDr!ZznKz}iWT#x& z?*#;H$`M0VC|a~1u_<(}WD>ogx(EvF6A6S8l0%9U<( zH||OBbh8Tnzz*#bV8&$d#AZNF$xF9F2{_B`^(zWNC}af(V~J+EZAbeC2%hjKz3V1C zj#%d%Gf(uyQ@0Y6CcP^CWkq`n+YR^W0`_qkDw333O<0FoO9()vP^!tZ{`0zsNQx~E zb&BcBU>GTP2svE2Tmd;~73mj!_*V8uL?ZLbx}{^l9+yvR5fas+w&0EpA?_g?i9@A$j*?LnmctPDQG|zJ`=EF}Vx8aMD^LrtMvpNIR*|RHA`ctK*sbG= zjN7Q)(|dGpC}$+nt~bupuKSyaiU}Ws{?Tha@$q}cJ;tvH>+MuPih+B4d$Zbq9$Y*U z)iA(-dK?Ov@uCDq48Zm%%t5uw1GrnxDm7*ITGCEF!2UjA`BqPRiUR`yNq^zz|A3wU zG(8DAnY-GW+PR2&7@In{Sla(XnMz5Rk^*5u4UvCiDQs@hvZXoiziv{6*i?fihVI|( zPrY8SOcOIh9-AzyJ*wF4hq%ojB&Abrf;4kX@^-p$mmhr}xxn#fVU?ydmD=21&S)s*v*^3E96(K1}J$6bi8pyUr-IU)p zcwa$&EAF$0Aj?4OYPcOwb-#qB=kCEDIV8%^0oa567_u6`9+XRhKaBup z2gwj*m#(}=5m24fBB#9cC?A$4CCBj7kanaYM&v754(b%Vl!gg&N)ZN_gO0mv(jM0# z>FC|FHi=FGlEt6Hk6H3!Yc|7+q{&t%(>3n#>#yx@*aS+bw)(2!WK#M0AUD~wID>yG z?&{p66jLvP1;!T7^^*_9F322wJB*O%TY2oek=sA%AUQT75VQ_iY9`H;ZNKFQELpZd z$~M`wm^Y>lZ8+F0_WCJ0T2td`bM+b`)h3YOV%&@o{C#|t&7haQfq#uJJP;81|2e+$ z|K#e~YTE87s+e0zCE2X$df`o$`8tQhmO?nqO?lOuTJ%GDv&-m_kP9X<5GCo1=?+LY z?!O^AUrRb~3F!k=H7Aae5W0V1{KlgH379eAPTwq=2+MlNcJ6NM+4ztXFTwI)g+)&Q7G4H%KH_(}1rq%+eIJ*3$?WwnZxPZ;EC=@`QS@|-I zyl+NYh&G>k%}GL}1;ap8buvF>x^yfR*d+4Vkg7S!aQ++_oNx6hLz6kKWi>pjWGO5k zlUZ45MbA=v(xf>Oeqhg8ctl56y{;uDG?A9Ga5aEzZB80BW6vo2Bz&O-}WAq>(PaV;*SX0=xXgI_SJ< zYR&5HyeY%IW}I>yKu^?W2$~S!pw?)wd4(#6;V|dVoa}13Oiz5Hs6zA zgICc;aoUt$>AjDmr0nCzeCReTuvdD1{NzD1wr*q@QqVW*Wi1zn;Yw1dSwLvTUwg#7 zpp~Czra7U~nSZZTjieZxiu~=}!xgV68(!UmQz@#w9#$0Vf@y%!{uN~w^~U_d_Aa&r zt2l>)H8-+gA;3xBk?ZV2Cq!L71;-tb%7A0FWziYwMT|#s_Ze_B>orZQWqDOZuT{|@ zX04D%y&8u@>bur&*<2??1KnaA7M%%gXV@C3YjipS4|cQH68OSYxC`P#ncvtB%gnEI z%fxRuH=d{L70?vHMi>~_lhJ@MC^u#H66=tx?8{HG;G2j$9@}ZDYUuTetwpvuqy}vW)kDmj^a|A%z(xs7yY2mU0#X2$un&MCirr|7 z%m?8+9aekm0x5hvBQ2J+>XeAdel$cy>J<6R3}*O^j{ObSk_Ucv$8a3_WPTd5I4HRT z(PKP5!{l*{lk_19@&{5C>TRV8_D~v*StN~Pm*(qRP+`1N12y{#w_fsXrtSt={0hJw zQ(PyWgA;;tBBDql#^2J(pnuv;fPn(H>^d<6BlI%00ylJZ?Evkh%=j2n+|VqTM~EUh zTx|IY)W;3{%x(O{X|$PS&x0?z#S2q-kW&G}7#D?p7!Q4V&NtA_DbF~v?cz6_l+t8e zoh1`dk;P-%$m(Ud?wnoZn0R=Ka$`tnZ|yQ-FN!?!9Wmb^b(R!s#b)oj9hs3$p%XX9DgQcZJE7B_dz0OEF6C zx|%jlqj0WG5K4`cVw!19doNY+(;SrR_txAlXxf#C`uz5H6#0D>SzG*t9!Fn|^8Z8; z1w$uiQzufUzvPCHXhGma>+O327SitsB1?Rn6|^F198AOx}! zfXg22Lm0x%=gRvXXx%WU2&R!p_{_1H^R`+fRO2LT%;He@yiekCz3%coJ=8+Xbc$mN zJ;J7*ED|yKWDK3CrD?v#VFj|l-cTgtn&lL`@;sMYaM1;d)VUHa1KSB5(I54sBErYp z>~4Jz41?Vt{`o7T`j=Se{-kgJBJG^MTJ}hT00H%U)pY-dy!M|6$v+-d(CkZH5wmo1 zc2RaU`p3_IJ^hf{g&c|^;)k3zXC0kF1>rUljSxd}Af$!@@R1fJWa4g5vF?S?8rg=Z z4_I!$dap>3l+o|fyYy(sX}f@Br4~%&&#Z~bEca!nMKV zgQSCVC!zw^j<61!7#T!RxC6KdoMNONcM5^Q;<#~K!Q?-#6SE16F*dZ;qv=`5 z(kF|n!QIVd*6BqRR8b8H>d~N@ab+1+{3dDVPVAo>{mAB#m&jX{usKkCg^a9Fef`tR z?M79j7hH*;iC$XM)#IVm&tUoDv!(#f=XsTA$)(ZE37!iu3Gkih5~^Vlx#<(M25gr@ zOkSw4{l}6xI(b0Gy#ywglot$GnF)P<FQt~9ge1>qp8Q^k;_Dm1X@Tc^{CwYb4v_ld}k5I$&u}avIDQ-D(_EP zhgdc{)5r_iTFiZ;Q)5Uq=U73lW%uYN=JLo#OS;B0B=;j>APk?|!t{f3grv0nv}Z%` zM%XJk^#R69iNm&*^0SV0s9&>cl1BroIw*t3R0()^ldAsq)kWcI=>~4!6fM#0!K%TS ziZH=H%7-f=#-2G_XmF$~Wl~Um%^9%AeNSk)*`RDl##y+s)$V`oDlnK@{y+#LNUJp1^(e89sed@BB z^W)sHm;A^9*RgQ;f(~MHK~bJRvzezWGr#@jYAlXIrCk_iiUfC_FBWyvKj2mBF=FI;9|?0_~=E<)qnjLg9k*Qd!_ zl}VuSJB%#M>`iZm*1U^SP1}rkkI};91IRpZw%Hb$tKmr6&H5~m?A7?+uFOSnf)j14 zJCYLOYdaRu>zO%5d+VeXa-Ai7{7Z}iTn%yyz7hsmo7E|{ z@+g9cBcI-MT~2f@WrY0dpaC=v{*lDPBDX}OXtJ|niu$xyit;tyX5N&3pgmCxq>7TP zcOb9%(TyvOSxtw%Y2+O&jg39&YuOtgzn`uk{INC}^Na_-V;63b#+*@NOBnU{lG5TS zbC+N-qt)u26lggGPcdrTn@m+m>bcrh?sG4b(BrtdIKq3W<%?WuQtEW0Z)#?c_Lzqj*DlZ zVUpEV3~mG#DN$I#JJp3xc8`9ex)1%Il7xKwrpJt)qtpq}DXqI=5~~N}N?0g*YwETZ z(NKJO5kzh?Os`BQ7HYaTl>sXVr!b8>(Wd&PU*3ivSn{;q`|@n*J~-3tbm;4WK>j3&}AEZ*`_!gJ3F4w~4{{PyLZklDqWo|X}D zbZU_{2E6^VTCg#+6yJt{QUhu}uMITs@sRwH0z5OqM>taO^(_+w1c ztQ?gvVPj<_F_=(ISaB~qML59HT;#c9x(;0vkCi2#Zp`;_r@+8QOV1Ey2RWm6{*J&9 zG(Dt$zF^7qYpo9Ne}ce5re^j|rvDo*DQ&1Be#Fvo#?m4mfFrNZb1#D4f`Lf(t_Fib zwxL3lx(Zp(XVRjo_ocElY#yS$LHb6yl;9;Ycm1|5y_praEcGUZxLhS%7?b&es2skI z9l!O)b%D=cXBa@v9;64f^Q9IV$xOkl;%cG6WLQ`_a7I`woHbEX&?6NJ9Yn&z+#^#! zc8;5=jt~Unn7!cQa$=a7xSp}zuz#Lc#Q3-e7*i`Xk5tx_+^M~!DlyBOwVEq3c(?`@ zZ_3qlTN{eHOwvNTCLOHjwg0%niFYm({LEfAieI+k;U2&uTD4J;Zg#s`k?lxyJN<$mK6>j?J4eOM@T*o?&l@LFG$Gs5f4R*p*V1RkTdCfv9KUfa< z{k;#JfA3XA5NQJziGd%DchDR*Dkld&t;6i9e2t7{hQPIG_uDXN1q0T;IFCmCcua-e z`o#=uS2_en206(TuB4g-!#=rziBTs%(-b1N%(Bl}ea#xKK9zzZGCo@<*i1ZoETjeC zJ)ll{$mpX7Eldxnjb1&cB6S=7v@EDCsmIOBWc$p^W*;C0i^Hc{q(_iaWtE{0qbLjxWlqBe%Y|A z>I|4)(5mx3VtwRBrano|P))JWybOHUyOY67zRst259tx;l(hbY@%Z`v8Pz^0Sw$?= zwSd^HLyL+$l&R+TDnbV_u+h{Z>n$)PMf*YGQ}1Df@Nr{#Gr+@|gKlnv?`s1rm^$1+ zic`WeKSH?{+E}0^#T<&@P;dFf;P5zCbuCOijADb}n^{k=>mBehDD6PtCrn5ZBhh2L zjF$TbzvnwT#AzGEG_Rg>W1NS{PxmL9Mf69*?YDeB*pK!&2PQ7!u6eJEHk5e(H~cnG zZQ?X_rtws!;Tod88j=aMaylLNJbgDoyzlBv0g{2VYRXObL=pn!n8+s1s2uTwtZc

YH!Z*ZaR%>WTVy8-(^h5J^1%NZ$@&_ZQ)3AeHlhL~=X9=fKPzFbZ;~cS**=W-LF1 z5F82SZ zG8QZAet|10U*jK*GVOA(iULStsUDMjhT$g5MRIc4b8)5q_a?ma-G+@xyNDk{pR*YH zjCXynm-fV`*;}%3=+zMj**wlCo6a{}*?;`*j%fU`t+3Korws%dsCXAANKkmVby*eJ z6`2%GB{+&`g2;snG`LM9S~>#^G|nZ|JMnWLgSmJ4!kB->uAEF0sVn6km@s=#_=d)y zzld%;gJY>ypQuE z!wgqqTSPxaUPoG%FQ()1hz(VHN@5sfnE68of>9BgGsQP|9$7j zGqN{nxZx4CD6ICwmXSv6&RD<-etQmbyTHIXn!Q+0{18=!p))>To8df$nCjycnW07Q zsma_}$tY#Xc&?#OK}-N`wPm)+2|&)9=9>YOXQYfaCI*cV1=TUl5({a@1wn#V?y0Yn z(3;3-@(QF|0PA}|w4hBWQbTItc$(^snj$36kz{pOx*f`l7V8`rZK}82pPRuy zxwE=~MlCwOLRC`y%q8SMh>3BUCjxLa;v{pFSdAc7m*7!}dtH`MuMLB)QC4B^Uh2_? zApl6z_VHU}=MAA9*g4v-P=7~3?Lu#ig)cRe90>@B?>})@X*+v&yT6FvUsO=p#n8p{ zFA6xNarPy0qJDO1BPBYk4~~LP0ykPV ztoz$i+QC%Ch%t}|i^(Rb9?$(@ijUc@w=3F1AM}OgFo1b89KzF6qJO~W52U_;R_MsB zfAC29BNUXpl!w&!dT^Zq<__Hr#w6q%qS1CJ#5Wrb*)2P1%h*DmZ?br)*)~$^TExX1 zL&{>xnM*sh=@IY)i?u5@;;k6+MLjx%m(qwDF3?K3p>-4c2fe(cIpKq#Lc~;#I#Wwz zywZ!^&|9#G7PM6tpgwA@3ev@Ev_w`ZZRs#VS4}<^>tfP*(uqLL65uSi9H!Gqd59C&=LSDo{;#@Isg3caF1X+4T}sL2B+Q zK*kO0?4F7%8mx3di$B~b&*t7y|{x%2BUg4kLFXt`FK;Vi(FIJ+!H zW;mjBrfZdNT>&dDfc4m$^f@k)mum{DioeYYJ|XKQynXl-IDs~1c(`w{*ih0-y_=t$ zaMDwAz>^CC;p*Iw+Hm}%6$GN49<(rembdFvb!ZyayLoqR*KBLc^OIA*t8CXur+_e0 z3`|y|!T>7+jdny7x@JHtV0CP1jI^)9){!s#{C>BcNc5#*hioZ>OfDv)&PAM!PTjS+ zy1gRZirf>YoGpgprd?M1k<;=SShCMn406J>>iRVnw9QxsR|_j5U{Ixr;X5n$ih+-=X0fo(Oga zB=uer9jc=mYY=tV-tAe@_d-{aj`oYS%CP@V3m6Y{)mZ5}b1wV<9{~$`qR9 zEzXo|ok?1fS?zneLA@_C(BAjE_Bv7Dl2s?=_?E9zO5R^TBg8Be~fpG?$9I; zDWLH9R9##?>ISN8s2^wj3B?qJxrSSlC6YB}Yee{D3Ex8@QFLZ&zPx-?0>;Cafcb-! zlGLr)wisd=C(F#4-0@~P-C&s%C}GvBhb^tTiL4Y_dsv@O;S56@?@t<)AXpqHx9V;3 zgB!NXwp`=%h9!L9dBn6R0M<~;(g*nvI`A@&K!B`CU3^FpRWvRi@Iom>LK!hEh8VjX z_dSw5nh-f#zIUDkKMq|BL+IO}HYJjMo=#_srx8cRAbu9bvr&WxggWvxbS_Ix|B}DE zk!*;&k#1BcinaD-w#E+PR_k8I_YOYNkoxw5!g&3WKx4{_Y6T&EV>NrnN9W*@OH+niSC0nd z#x*dm=f2Zm?6qhY3}Kurxl@}d(~ z<}?Mw+>%y3T{!i3d1%ig*`oIYK|Vi@8Z~*vxY%Od-N0+xqtJ*KGrqo*9GQ14WluUn z+%c+og=f0s6Mcf%r1Be#e}&>1n!!ZxnWZ`7@F9ymfVkuFL;m6M5t%6OrnK#*lofS{ z=2;WPobvGCu{(gy8|Mn(9}NV99Feps6r*6s&bg(5aNw$eE ztbYsrm0yS`UIJ?Kv-EpZT#76g76*hVNg)L#Hr7Q@L4sqHI;+q5P&H{GBo1$PYkr@z zFeVdcS?N1klRoBt4>fMnygNrDL!3e)k3`TXoa3#F#0SFP(Xx^cc)#e2+&z9F=6{qk z%33-*f6=+W@baq){!d_;ouVthV1PREX^ykCjD|%WUMnNA2GbA#329aEihLk~0!!}k z)SIEXz(;0lemIO{|JdO{6d|-9LePs~$}6vZ>`xYCD(ODG;OuwOe3jeN;|G$~ml%r* z%{@<9qDf8Vsw581v9y+)I4&te!6ZDJMYrQ*g4_xj!~pUu#er`@_bJ34Ioez)^055M$)LfC|i*2*3E zLB<`5*H#&~R*VLYlNMCXl~=9%o0IYJ$bY+|m-0OJ-}6c@3m<~C;;S~#@j-p?DBdr<><3Y92rW-kc2C$zhqwyq09;dc5;BAR#PPpZxqo-@e_s9*O`?w5 zMnLUs(2c-zw9Pl!2c#+9lFpmTR>P;SA#Id;+fo|g{*n&gLi}7`K)(=tcK|?qR4qNT z%aEsSCL0j9DN$j8g(a+{Z-qPMG&O)H0Y9!c*d?aN0tC&GqC+`%(IFY$ll~!_%<2pX zuD`w_l)*LTG%Qq3ZSDE)#dt-xp<+n=3&lPPzo}r2u~>f8)mbcdN6*r)_AaTYq%Scv zEdwzZw&6Ls8S~RTvMEfX{t@L4PtDi{o;|LyG>rc~Um3;x)rOOGL^Bmp0$TbvPgnwE zJEmZ>ktIfiJzdW5i{OSWZuQWd13tz#czek~&*?iZkVlLkgxyiy^M~|JH(?IB-*o6% zZT8+svJzcVjcE0UEkL_5$kNmdrkOl3-`eO#TwpTnj?xB}AlV2`ks_Ua9(sJ+ok|%b z=2n2rgF}hvVRHJLA@9TK4h#pLzw?A8u31&qbr~KA9;CS7aRf$^f1BZ5fsH2W8z}FU zC}Yq76IR%%g|4aNF9BLx6!^RMhv|JYtoZW&!7uOskGSGL+}_>L$@Jg2Vzugq-NJW7 zzD$7QK7cftU1z*Fxd@}wcK$n6mje}=C|W)tm?*V<<{;?8V9hdoi2NRm#~v^#bhwlc z5J5{cSRAUztxc6NH>Nwm4yR{(T>0x9%%VeU&<&n6^vFvZ{>V3RYJ_kC9zN(M(` zp?1PHN>f!-aLgvsbIp*oTZv4yWsXM2Q=C}>t7V(iX*N8{aoWphUJ^(n3k`pncUt&` ze+sYjo)>>=I?>X}1B*ZrxYu`|WD0J&RIb~ zPA_~u)?&`}JPwc1tu=OlKlJ3f!9HXa)KMb|2%^~;)fL>ZtycHQg`j1Vd^nu^XexYkcae@su zOhxk8ws&Eid_KAm_<}65zbgGNzwshR#yv&rQ8Ae<9;S^S}Dsk zubzo?l{0koX8~q*{uA%)wqy*Vqh4>_Os7PPh-maB1|eT-4 zK>*v3q}TBk1QlOF!113XOn(Kzzb5o4Dz@?q3aEb9%X5m{xV6yT{;*rnLCoI~BO&SM zXf=CHLI>kaSsRP2B{z_MgbD;R_yLnd>^1g`l;uXBw7|)+Q_<_rO!!VaU-O+j`u%zO z1>-N8OlHDJlAqi2#z@2yM|Dsc$(nc>%ZpuR&>}r(i^+qO+sKfg(Ggj9vL%hB6 zJ$8an-DbmKBK6u6oG7&-c0&QD#?JuDYKvL5pWXG{ztpq3BWF)e|7aF-(91xvKt047 zvR{G@KVKz$0qPNXK*gt*%qL-boz-*E;7LJXSyj3f$7;%5wj)2p8gvX}9o_u}A*Q|7 z)hjs?k`8EOxv1zahjg2PQDz5pYF3*Cr{%iUW3J+JU3P+l?n%CwV;`noa#3l@vd#6N zc#KD2J;5(Wd1BP)`!IM;L|(d9m*L8QP|M7W#S7SUF3O$GFnWvSZOwC_Aq~5!=1X+s z6;_M++j0F|x;HU6kufX-Ciy|du;T%2@hASD9(Z)OSVMsJg+=7SNTAjV<8MYN-zX5U zVp~|N&{|#Z)c6p?BEBBexg4Q((kcFwE`_U>ZQotiVrS-BAHKQLr87lpmwMCF_Co1M z`tQI{{7xotiN%Q~q{=Mj5*$!{aE4vi6aE$cyHJC@VvmemE4l_v1`b{)H4v7=l5+lm^ ztGs>1gnN(Vl+%VuwB+|4{bvdhCBRxGj3ady^ zLxL@AIA>h@eP|H41@b}u4R`s4yf9a2K!wGcGkzUe?!21Dk)%N6l+#MP&}B0%1Ar*~ zE^88}(mff~iKMPaF+UEp5xn(gavK(^9pvsUQT8V;v!iJt|7@&w+_va`(s_57#t?i6 zh$p!4?BzS9fZm+ui`276|I307lA-rKW$-y^lK#=>N|<-#?WPPNs86Iugsa&n{x%*2 zzL_%$#TmshCw&Yo$Ol?^|hy{=LYEUb|bMMY`n@#(~oegs-nF){0ppwee|b{ca)OXzS~01a%cg&^ zp;}mI0ir3zapNB)5%nF>Sd~gR1dBI!tDL z&m24z9sE%CEv*SZh1PT6+O`%|SG>x74(!d!2xNOt#C5@I6MnY%ij6rK3Y+%d7tr3&<^4XU-Npx{^`_e z9$-|@$t`}A`UqS&T?cd@-+-#V7n7tiZU!)tD8cFo4Sz=u65?f#7Yj}MDFu#RH_GUQ z{_-pKVEMAQ7ljrJ5Wxg4*0;h~vPUI+Ce(?={CTI&(RyX&GVY4XHs>Asxcp%B+Y9rK z5L$q94t+r3=M*~seA3BO$<0%^iaEb2K=c7((dIW$ggxdvnC$_gq~UWy?wljgA0Dwd`ZsyqOC>)UCn-qU5@~!f znAWKSZeKRaq#L$3W21fDCMXS;$X(C*YgL7zi8E|grQg%Jq8>YTqC#2~ys%Wnxu&;ZG<`uZ1L<53jf2yxYR3f0>a;%=$SYI@zUE*g7f)a{QH^<3F?%({Gg)yx^zsdJ3^J2 z#(!C3qmwx77*3#3asBA(jsL`86|OLB)j?`0hQIh>v;c2A@|$Yg>*f+iMatg8w#SmM z<;Y?!$L--h9vH+DL|Wr3lnfggMk*kyGH^8P48or4m%K^H-v~`cBteWvnN9port02u zF;120HE2WUDi@8?&Oha6$sB20(XPd3LhaT~dRR2_+)INDTPUQ9(-370t6a!rLKHkIA`#d-#WUcqK%pMcTs6iS2nD?hln+F-cQPUtTz2bZ zq+K`wtc1;ex_iz9?S4)>Fkb~bj0^VV?|`qe7W02H)BiibE9=_N8=(5hQK7;(`v7E5Mi3o? z>J_)L`z(m(27_&+89P?DU|6f9J*~Ih#6FWawk`HU1bPWfdF?02aY!YSo_!v$`&W znzH~kY)ll^F07=UNo|h;ZG2aJ<5W~o7?*${(XZ9zP0tTCg5h-dNPIM=*x@KO>a|Bk zO13Cbnbn7+_Kj=EEMJh4{DW<))H!3)vcn?_%WgRy=FpIkVW>NuV`knP`VjT78dqzT z>~ay~f!F?`key$EWbp$+w$8gR1RHR}>wA8|l9rl7jsT+>sQLqs{aITUW{US&p{Y)O zRojdm|7yoA_U+`FkQkS?$4$uf&S52kOuUaJT9lP@LEqjKDM)iqp9aKNlkpMyJ76eb zAa%9G{YUTXa4c|UE>?CCv(x1X3ebjXuL&9Dun1WTlw@Wltn3zTareM)uOKs$5>0tR zDA~&tM~J~-YXA<)&H(ud)JyFm+d<97d8WBr+H?6Jn&^Ib0<{6ov- ze@q`#Y%KpD?(k{if5-M(fO3PpK{Wjqh)7h+ojH ztb=h&vmy0tn$eA8_368TlF^DKg>BeFtU%3|k~3lZAp(C$&Qjo9lR<#rK{nVn$)r*y z#58_+t=UJm7tp|@#7}6M*o;vn7wM?8Srtc z3ZFlKRDYc^HqI!O9Z*OZZ8yo-3ie9i8C%KDYCfE?`rjrf(b&xBXub!54yaZY2hFi2w2asEOiO8;Hru4~KsqQZMrs+OhO8WMX zFN0=EvME`WfQ85bmsnPFp|RU;GP^&Ik#HV(iR1B}8apb9W9)Nv#LwpED~%w67o;r! zVzm@zGjsl)loBy6p>F(G+#*b|7BzZbV#E0Pi`02uAC}D%6d12TzOD19-9bhZZT*GS zqY|zxCTWn+8*JlL3QH&eLZ}incJzgX>>i1dhff}DJ=qL{d?yv@k33UhC!}#hC#31H zOTNv5e*ozksj`4q5H+75O70w4PoA3B5Ea*iGSqA=v)}LifPOuD$ss*^W}=9kq4qqd z6dqHmy_IGzq?j;UzFJ*gI5)6qLqdUL;G&E*;lnAS+ZV1nO%OdoXqw(I+*2-nuWjwM-<|XD541^5&!u2 z1XflFJp(`^D|ZUECbaoqT5$#MJ=c23KYpBjGknPZ7boYRxpuaO`!D6C_Al?T$<47T zFd@QT%860pwLnUwer$BspTO9l1H`fknMR|GC?@1Wn`HscOe4mf{KbVio zahne0&hJd0UL#{Xyz=&h@oc>E4r*T|PHuNtK6D279q!2amh%r#@HjaN_LT4j>{&2I z?07K#*aaZ?lNT6<8o85cjZoT~?=J&Xd35I%JJom{P=jj?HQ5yfvIR8bd~#7P^m%B-szS{v<)7i?#at=WA+}?r zwMlc-iZv$GT};AP4k2nL70=Q-(+L_CYUN{V?dnvG-Av+%)JxfwF4-r^Z$BTwbT!Jh zG0YXK4e8t`3~){5Qf6U(Ha0WKCKl^zlqhqHj~F}DoPV#yHqLu+ZWlv2zH29J6}4amZ3+-WZkR7(m{qEG%%57G!Yf&!Gu~FDeSYmNEkhi5nw@#6=Bt& zOKT!UWVY-FFyq1u2c~BJ4F`39K7Vw!1U;aKZw)2U8hAb&7ho|FyEyP~D<31{_L>RrCU>eEk-0)TBt5sS5?;NwAdRzRj5qRSD?J6 ze9ueq%TA*pgwYflmo`=FnGj2r_u2!HkhE5ZbR_Xf=F2QW@QTLD5n4h(?xrbOwNp5` zXMEtm`m52{0^27@=9VLt&GI;nR9S)p(4e+bAO=e4E;qprIhhclMO&7^ThphY9HEko z#WfDFKKCcf%Bi^umN({q(avHrnTyPH{o=sXBOIltHE?Q65y_At<9DsN*xWP|Q=<|R z{JfV?B5dM9gsXTN%%j;xCp{UuHuYF;5=k|>Q=;q zU<3AEYawUG;=%!Igjp!FIAtJvoo!*J^+!oT%VI4{P=XlbYZl;Dc467Nr*3j zJtyn|g{onj!_vl)yv)Xv#}(r)@25OHW#|eN&q7_S4i2xPA<*uY9vU_R7f};uqRgVb zM%<_N3ys%M;#TU_tQa#6I1<+7Bc+f%mqHQ}A@(y^+Up5Q*W~bvS9(21FGQRCosvIX zhmsjD^OyOpae*TKs=O?(_YFjSkO`=CJIb*yJ)Pts1egl@dX6-YI1qb?AqGtIOir&u zyn>qxbJhhJi9SjK+$knTBy-A)$@EfzOj~@>s$M$|cT5V!#+|X`aLR_gGYmNuLMVH4 z(K_Tn;i+fR28M~qv4XWqRg~+18Xb?!sQ=Dy)oRa)Jkl{?pa?66h$YxD)C{F%EfZt| z^qWFB2S_M=Ryrj$a?D<|>-Qa5Y6RzJ$6Yp`FOy6p2lZSjk%$9guVsv$OOT*6V$%TH zMO}a=JR(1*u`MN8jTn|OD!84_h${A)_eFRoH7WTCCue9X73nbD282V`VzTH$ckVaC zalu%ek#pHxAx=0migDNXwcfbK3TwB7@T7wx2 zGV7rS+2g9eIT9>uWfao+lW2Qi9L^EBu#IZSYl0Q~A^KYbQKwNU(YO4Xa1XH_>ml1v z#qS;P!3Lt%2|U^=++T`A!;V-!I%upi?<#h~h!X`p7eP!{+2{7DM0$yxi9gBfm^W?M zD1c)%I7N>CG6250NW54T%HoCo^ud#`;flZg_4ciWuj4a884oWUYV(#VW`zO1T~m(_ zkayymAJI)NU9_0b6tX)GU+pQ3K9x=pZ-&{?07oeb1R7T4RjYYbfG^>3Y>=?dryJq& zw9VpqkvgVB?&aK}4@m78NQhTqZeF=zUtBkJoz8;6LO<4>wP7{UPEs1tP69;v919I5 zzCqXUhfi~FoK5niVU~hQqAksPsD@_|nwH4avOw67#fb@Z5_OS=$eP%*TrPU%HG<-A z`9)Y3*SAdfiqNTJ2eKj8B;ntdqa@U46)B+odlH)jW;U{A*0sg@z>-?;nN}I=z3nEE@Bf3kh1B zdqT{TWJvb#AT&01hNsBz8v(OwBJSu#9}A6Y!lv|`J#Z3uVK1G`0$J&OH{R?3YVfk% z9P3HGpo<1uy~VRCAe&|c4L!SR{~^0*TbVtqej3ARx(Okl5c>m~|H9ZwKVHc_tCe$hsqA`l&h7qPP5xBgtwu!; zzQyUD<6J!M5fsV-9P?C9P49qnXR+iXt#G_AS2N<6!HZ(eS`|-ndb|y!(0Y({2 z4aF~GO8bHM7s+wnhPz>sa!Z%|!qWk*DGr)azB}j6bLe#FQXV4aO>Eo7{v`0x=%5SY zy&{kY+VLXni6pPJYG_Sa*9hLy-s$79$zAhkF)r?9&?UaNGmY9F$uf>iJ~u@Q;sydU zQaN7B>4B*V;rtl^^pa3nFh$q*c&sx^Um}I)Z)R&oLEoWi3;Yv6za?;7m?fZe>#_mS z-EGInS^#UHdOzCaMRSLh7Mr0}&)WCuw$4&K^lx{;O+?Q1p5PD8znQ~srGrygJ?b~Q5hIPt?Wf2)N?&Dae4%GRcRKL(a-2koctrcvxSslXn-k9cYS|<-KJ#+$Wo>}yKKh*3Q zHsK(4-Jv!9R3*FKmN$Z#^aZcACGrlGjOe^#Z&DfPyS-1bT9OIX~-I-5lN6Y>M}dvivbs2BcbPcaNH%25-xMkT$>*soDJ) z27;};8oCYHSLF0VawZFn8^H;hIN=J457@eoI6s2P87QN6O`q8coa;PN$mRZ>2Vv+! zQj1}Tvp8?>yyd_U>dnhx%q~k*JR`HO=43mB?~xKAW9Z}Vh2b0<(T89%eZ z57kGs@{NUHM>|!+QtqI@vE8hp`IIGc`A9Y{p?c;@a!zJFmdaCJ;JmzOJ8)B1x{yZp zi!U{Wh-h+u6vj`2F+(F6gTv*cRX7MR z9@?>is`MSS1L#?PaW6BWEd#EX4+O1x6WdU~LZaQ^Quow~ybz*aAu{ZMrQ;yQ8g)-qh>x z^}@eFu1u7+3C0|hRMD1{MEn(JOmJ|wYHqGyn*xt-Y~J3j@nY56i)sgNjS4n@Q&p@@^>HQjzNaw#C9=TbwzDtiMr2a^}bX< zZE%HU^|CnS`WYVcs}D)+fP#bW0+Q#l#JC+!`OlhffKUCN8M-*CqS;VQX`If78$as0 z=$@^NFcDpTh~45heE63=x5nmP@4hBaFn(rmTY2Yj{S&k;{4W!0Nu9O5pK30}oxM7{ z>l4cKb~9D?N#u_AleD<~8XD@23sY^rt&fN%Q0L=Ti2bV#px`RhM$}h*Yg-iC4A+rI zV~@yY7!1}-@onsZ)@0tUM23cN-rXrZYWF#!V-&>vds8rP+w0t{?~Q zT^LN*lW==+_ifPb+-yMh9JhfcYiXo_zWa`ObRP9_En3P))Qyu0qPJ3*hiFSu>Vt-j z<*HWbiP2#BK@nt<g|pe3 zfBKS@i;ISkorx@cOIx9}p^d8Gis%$)))%ByVYU^KG#eE+j1p;^(Y1ndHnV&YuQZm~ zj;f+mf>0ru!N`)_p@Ls<& z`t+JDx7}R568Q|8`4A}G@t8Wc?SOXunyW5C-AWoB@P>r}uwFY*=?=!K@J(!t@#xOuPXhFS@FTf6-7|%k;nw2%Z+iHl219Ho1!bv(Ee0|ao!Rs%Jl0@3suGrOsb_@VM;(xzrf^Cbd;CK3b%a|ih-fG)`Rd00O74=sQYW~Ve z#fl!*(fo~SIQ5-Sl?1@o7-E*|SK|hoVEKzxeg!$KmQLSTN=5N`rYeh$AH&x}JMR+5dq|~FUy&Oj%QIy;HNr;V*7cQC+ka>LAwdU)?ubI@W z={eg%A&7D**SIj$cu=CN%vN^(_JeIHMUyejCrO%C3MhOcVL~Niu;8WYoN}YVhb+=- zR}M3p|H0`E2Id99y#03r`8$s0t*iD>`^7EPm1~guC)L~uW#O~>I85Q3Nj8(sG<@T| zL^e~XQt9O0AXQ^zkMdgzk5bdYttP~nf-<831zulL>>ghTFii$lg3^80t8Gb*x1w5| zN{kZuv`^8Fj=t(T*46M=S$6xY@0~AvWaGOYOBTl0?}KTkplmGn-*P(X=o-v^48OY} zi11-+Y}y)fdy_tI;*W(>#qzvgQZ52t!nrGsJEy!c86TKIN(n|!&ucCduG$XaIapI z{(Z9gZANsI={A=5Aorgq2H25Dd}H5@-5=j=s{f`%^>6b5qkm_2|3g>r-^amf=B_xV zXg*>aqxXZ6=VUI4$})ypDMy$IKkgJ;V>077T9o#OhpFhKtHP_4mnjS5QCgGe<;~Xe zt<2ZhL7?JL6Mi|U_w?;?@4OD@=4EB2op_s)N-ehm#7`zSU#7itU$#%^ncqjc`9HCG zfj;O1T+*oTkzRi-6NN`oS3w3$7ZB37L>PcN$C$L^qqHfiYO4_>0_qCw0r@FEMj=>}}%q_`d#pUT;c?=gI zqTGpiY4Z;Q(B~#hXIVBFbi#dO=cOdmOqD0|An?7nMdrm2^C>yw*dQ=#lf8)@DvXK; z$MXp}QZgnE!&L73x0LZX_bCdD4lRY$$^?9dt1RwCng{lIpbb%Ej%yOh{@76yEyb}K zXZy%^656Sk3BLKbalcc>Dt5iDzo^tj2!wnDL(X;urJfpkWrab!frFSC6Q7m zuoqN!(t=L&+Ov&~9mz(yEB`MK%RPXS>26Ww5(F;aZ zR@tPAw~=q2ioOiynxgBqE&3-R-@6yCo0*mE;#I^c!=g~HyyjGA6}|<(0EseKDTM4w z94YnCO^VYIUY@}x8kr;;El-cFHVO<$6;-UdmUB|J8R*Wf$a37gVgYT|w5^KkYe=(i zMkA$%7;^a*$V+}e%S~&*^^O;AX9NLt@cIPc*v!lKZ)(zahAsUj%PJot19ErFU=Uk( z9Hw;Lb`V+BzVpMu;TGB9}y~ff)^mbEmF?g{{7_0SR zPgp*n)l{?>7-Ji;eWG{ln$)Bro+UJAQo6W2-23d@SI=HiFV3hR2OUcAq_9q~ye)o@ zq8WZvhg`H(?1AUZ-NM%_Cuj}eb{4wOCnqs^E1G9U4HKjqaw@4dsXWP#$wx^}XPZ0F zywsJ0aJHA>AHc^q#nhQjD3!KDFT6FaDioJ#HsZU7Wo?8WH19TJ%OMDz$XH5J4Cjdt z@crE;#JNG`&1H8ekB(R4?QiiZ55kztsx}pQti}gG0&8`dP=d(8aCLOExd*Sw^WL`Q zHvZ(u`5A58h?+G&GVsA;pQNNPFI)U@O`#~RjaG(6Y<=gKT2?1 z*pCUGU)f??VlyP64P@uT`qh?L03ZQyLOBn?EKwH+IG{XvTh5|NldaSV_n~DK&F1aa znq~C_lCQHMfW6xib%a2m!h&%J)aXb{%-0!HCcW|kzaoSwPMhJ6$KL|F~Sx(tctbwfkgV;#KZlEmJN5&l5XF9eD;Kqb<| z>os)CqC^qF8$be|v;)LY{Gh@c0?a??k7M7&9CH+-B)t&T$xeSzCs30sf8O-+I#rq} z&kZj5&i>UyK9lDjI<*TLZ3USVwwpiE5x8<|{Db z3`HX3+Tt>1hg?+uY{^wC$|Tb7ud@3*Ub?=2xgztgv6OOz0G z-4VRyIChHfegUak^-)-P;VZY@FT64#xyo=+jG<48n2%wcx`ze6yd51(!NclmN=$*kY=#uu#>=yAU-u4I9Bt0n_6ta?&9jN+tM_5_3RH);I zxTN4n$EhvKH%TmOh5mq|?Cx$m>$Ed?H7hUEiRW^lnW+}ZoN#;}aAuy_n189qe1Juk z6;QeZ!gdMAEx4Na;{O*j$3F3e?FLAYuJ2iuMbWf8Ub6(nDo?zI5VNhN@ib6Yw_4P)GY^0M7TJwat z2S*2AcP}e0tibZ@k&htTD&yxT9QRG0CEq$;obfgV^&6YVX9B9|VJf`1aS_#Xk>DFo zwhk?~)>XlP5(u~UW0hP7dWZuCuN4QM24Td&j^7~)WQ6YeCg)njG*ri}tTcG-NxX}p zNB>kcxd5ipW@tN3=6r@Jgm#rgrK*dXA!gxy6fAvP7$)8)Vc~PPQ|`( zPy|bG1sUz958-!zW^j(8ILV%QC@x`~PDFczboZqWjvSU<9O3!TQ&xYi%?Y0AiVBLV z%R?#1L#G&xw*RZPsrwF?)B5+MSM(b$L;GLnRsSU!_$N;6pD97~H}`c>0F`&E_FCNE z_)Q*EA1%mOp`z>+h&aqlLKUD9*w?D>stDeBRdR*AS9)u;ABm7w1}eE|>YH>YtMyBR z^e%rPeZzBx_hj?zhJVNRM_PX(O9N#^ngmIJ0W@A)PRUV7#2D!#3vyd}ADuLry;jdn zSsTsHfQ@6`lH z^GWQf?ANJS>bBO-_obBL$Apvakhr1e5}l3axEgcNWRN$4S6ByH+viK#CnC1|6Xqj& z*_i7cullAJKy9GBAkIxUIzsmN=M|(4*WfBhePPHp?55xfF}yjeBld7+A7cQPX8PE-|Pe_xqboE;2AJb5ifrEfr86k&F0+y!r`-urW}OXSkfz2;E``UTrGSt^B)7&#RSLTQitk=mmPKUKP`uGQ4)vp_^$^U`2Jjq zeul!ptEpa%aJo0S(504oXPGdWM7dAA9=o9s4-{>z*pP zJ31L#|L?YR;^%+>YRJrLrFC=5vc;0{hcxDKF z!ntmgO>rVDaGmRpMI7-+mv(j~;s_LARvcpkXj|{GHu1c<1 zKI)#7RE~Dizu1lG>p-PcY2jX#)!oJlBA$LHnTUWX=lu``E)vhf9h4tYL-juZ`e|Kb z=F?C;Ou)h^cxB;M-8@$ZSH0jkVD>x-XS$ePV1vlU8&CG))4NgU(=XFH=Jb1IB7dBysS+94}Y>sjS(&YcJwhn zifzA|g$D5rW89vkJSv()I+Th4R&C$g-!CB30xkh%aw4po3$@DK2fW>}enE2YPt&{C~j}`>RYICK{ zYAPfZ&%`R}u6MYo<>d`^O#Q(dM{3>T^%J{Vu;lr#Utg4x9!Z9J%iXs(j+dn&SS1_2 zzxGtMnu^`d%K4Xq4Ms-ErG3_7n?c(3T!?rvyW=G<7_XKDv*ox`zN*^BVwUoqh{D7o zdEiq;Zp6}k_mCIAVTUcMdH|fo%L#qkN19X$%b1#Oko|u4!M*oRqdBa3z98{H#g=d%5X&D#NXhLh`nUjxi8@3oo(AgeItdJ zIrt9ieHI1GiwHiU4Cba-*nK@eHI4uj^LVmVIntU@Gwf^t6i3{;SfLMCs#L;s;P4s5oqd^}8Uil!NssP>?!K z07nAH>819U=^4H6l-Dhy`^Q6DV^}B9^aR0B%4AH=D&+dowt9N}zCK+xHnXb-tsKaV6kjf;Wdp#uIZ_QsI4ralE>MWP@%_5eN=MApv92( z09SSB#%eE|2atm9P~X2W2F-zJD+#{q9@1}L2fF|Lzu@1CAJq*d6gA8*Jjb;<+Asih zctE|7hdr5&b-hRhVe}PN z$0G{~;pz1yhkbwuLkfbvnX=<7?b(1PhxAmefKn$VS6Sv)t-UypwhEs3?*E=(pc%Dlul1V~OdWvdf z{WBX?lhfO_g$$X~hm^Bhl@U0t<|beYgT)2L_C(z@B^-63c9Ak2*Aa)iOMylfl|qyNQdO#yoJ?m2FOkhZ1ou@G%+^m z#!#(gTv8nx^34(HddDp|dcFl@&eh+&FFJc@^FL3fV2?u&9Wt|Yp3&MS)e+ez0g~Ys zY7d0n^)+ z0@K^GJTLN?XAV(0F6e>o>HCGJU5(8WsSFErs0FsO=O1u$=T~xx7HYK{7C>-IGB8U+ z&G^Vy>uY}Bq7HX-X`U^nNh+11GjG-)N1l_tG<^4Tu4+4X9KO9IrdH+eXGk|G6Tc(U zU~g7BoO!{elBk>;uN-`rGQP-7qIf9lQhj-=_~0Qyszu>s$s0FrJatSylv!ol&{29~ z7S4fv&-UBOF&cR@xpuW*{x9$R;c_ALt?{+dI&HoBKG-!EY{yE=>aWhlmNhHlCXc(B zuA-zI*?Z9ohO$i8s*SEIHzVvyEF$65b5m=H*fQ)hi*rX8 zKlPqjD*Ix1tPzfR_Z3bO^n32iQ#vhjWDwj6g@4S?_2GyjiGdZZRs3MLM zTfl0_Dsn=CvL`zRey?yi)&4TpF&skAi|)+`N-wrB_%I_Osi~)9`X+`Z^03whrnP7f z?T`*4Id`J@1x#T~L(h5^5z%Cok~U|&g&GpCF%E4sB#i3xAe>6>24%Kuu=)=HRS;Pu2wghgTFa zHqm#sa{7-~{w_039gH0vrOm&KPMiPmuPRpAQTm5fkPTZVT&9eKuu%Riu%-oMQl2X6 z{Bnx`3ro^Z$}rVzvUZsk9T)pX|4%sY+j0i)If_z-9;a^vr1YN>=D(I7PX){_JTJ&T zPS6~9iDT{TFPn}%H=QS!Tc$I9FPgI<0R7?Mu`{FTP~rRq(0ITmP1yrJdy|m;nWmDelF-V^y7*UEVvbxNv0sHR?Q=PVYRuZinR(;RjVAG zm&qlSYvaiIbVEqBwyDaJ8LVmiCi{6ESF4pO?U&7pk&CASm6vuB;n-RauPFzdr!C%1 z8pjdSUts7EbA4Kg(01zK!ZU<-|d zU&jWswHnSLIg&mTR;!=-=~z(#!UsXt%NJR|^teM8kG@8Qg_0^6Jqfn&(eENtP8D7K zvnll3Y%7yh1Ai~0+l6dAG|lEGe~Oa+3hO>K2}{ulO?Vf*R{o2feaRBolc;SJg)HXHn4qtzomq^EM zb)JygZ=_4@I_T=Xu$_;!Q`pv6l)4E%bV%37)RAba{sa4T*cs%C!zK?T8(cPTqE`bJ zrBWY`04q&+On`qH^KrAQT7SD2j@C>aH7E8=9U*VZPN-(x>2a++w7R$!sHH+wlze2X)<<=zC_JJvTdY7h&Jum?s?VRV)JU`T;vjdi7N-V)_QCBzI zcWqZT{RI4(lYU~W0N}tdOY@dYO8Rx5d7DF1Ba5*U7l$_Er$cO)R4dV zE#ss{Dl`s#!*MdLfGP>?q2@GSNboVP!9ZcHBZhQZ>TJ85(=-_i4jdX5A-|^UT}~W{CO^Lt4r;<1ps@s|K7A z90@6x1583&fobrg9-@p&`Gh+*&61N!$v2He2fi9pk9W2?6|)ng7Y~pJT3=g~DjTcYWjY9gtZ5hk*1Qf!y2$ot@0St$@r8|9^GMWEE>iB~etL zXYxn#Rvc`DV&y93@U$Z91md1qVtGY*M(=uCc}@STDOry@58JNx`bUH}EIb(n6I}i? zSYJOZ2>B6&Payu+@V!gxb;)_zh-{~qtgVwQ-V;vK7e0^Ag_$3+g+{xSVudVOY_p-R z$sXhpFSk7je2lk5)7Y2;Z847E1<;5?;z(I)55YFtgF!J;NT|eVi}q^*2sM}zyM{+s zD0phl+J>k1E7cZEGmP?1-3~RE;R$q(I5}m?MX8xi?6@0f#rD8Cjkpv1GmL5HVbTnM zAQ&4-rbkpdaoLp~?ZoW>^+t0t1t%GO2B;ZD4?{qeP+qsjOm{1%!oy1OfmX?_POQJ4 zGwvChl|uE;{zGoO?9B_m{c8p(-;_yq?b^jA({}iQG35?7H7`1cm`BGyfuq7z1s~T| zm88HpS{z54T{jxC=>kZ=Z#8G@uya3tt0$xST5V$-V<;6MA66VFg}`LLU8L=q3DmkU z)P^X8pg`ndMY*>gr{6~ur^Q@Z8LNQf*6wkP03K<|M*+cDc#XKZ`Z0$1FkI-IDRw#| za52W4MyHlDABs~AQu7Duebjgc}02W;1jgBx&I@TMDXU`LJutQ?@r%1z`W zlB8G-U$q37G1ob>Er8j0$q@OU3IwG#8HsvJM#)j=Y%~#zY`jaG%5;!(kY3*a^t>(qf6>I zpAJpF%;FQ?BhDSsVG27tQEG*CmWhl4)Ngp%}D?U0!nb1=)1M==^B)^$8Li$boCY$S4U;G^A!?24nSYHra{< zSNapX#G+0BTac|xh`w&}K!);$sA3ay%^a2f?+^*9Ev8ONilfwYUaDTMvhqz2Ue2<81uuB71 zAl|VEOy%GQ7zxAJ&;V^h6HOrAzF=q!s4x)Mdlmp{WWI=gZRk(;4)saI0cpWJw$2TJcyc2hWG=|v^1CAkKYp;s_QmU?A;Yj!VQ1m-ugzkaJA(wQ_ zah00eSuJg<5Nd#OWWE?|GrmWr+{-PpE_Dbqs&2`BI=<%ggbwK^8VcGiwC-6x`x|ZY z1&{Vj*XIF2$-2Lx?KC3UNRT z&=j7p1B(akO5G)SjxXOjEzujDS{s?%o*k{Ntu4*X z;2D|UsC@9Wwk5%)wzTrR`qJX!c1zDZXG>-Q<3Z)7@=8Y?HAlj_ZgbvOJ4hPlcH#Iw z!M-f`OSHF~R5U`p(3*JY=kgBZ{Gk;0;bqEu%A;P6uvlZ0;BAry`VUoN(*M9NJ z%CU2_w<0(mSOqG;LS4@`p(3*Z7jC|Khm5-i>FcYr87};_J9)XKlE}(|HSfnA(I3)I zfxNYZhs#E6k5W(z9TI2)qGY&++K@Z?bd;H%B@^!>e2Wi@gLk)wC)T93gTxdRPU7uh z)`$-m(G2I5AuK52aj!fMJR|d^H?0X~+4xSpw zqNRtq5r8hic*{eAwUT<=gI5uXLg)o5mg4XnO^T+Rd+{l)<$Aqp{+RxhNYuX^45W0k z5$t%+7R;dX$`s6CYQYcims>5bNt+k&l_t%C9D-6sYVm%Y8SRC#kgRh*%2kqMg2ewb zp_X*$NFU%#$PuQ@ULP>h9Xw`cJ>J-ma8lU`n*9PcWFpE%x0^}(DvOVe2jz@ z0^2QOi0~t!ov?jI{#bw~`Aj5ymQW@eruRg`ZNJ5IT5_5AHbQ?|C>_7rwREf2e2x&L zlV8xdOkp_*+wdaqE?6bmdrFfaGepcj=0AI<+c=Tg^WB9BhFx?SvwoVdTEm&zPy@Vs zPs2mVPiw1n_h?Xi6!+w)ypsFXXuM>gIY(J+1N6r!sJ{+r1%BzRF20!D;bN>L^?O8n z(5|x2p^Q6X`!pm3!MMFET5`nJXn>tK`fFAj5Eo&t6;F>TU_4G93YGyzvF2_fB& zfE8(dq?R@@&Wh8~%G~rDt1+e)96O5)by_%;G~Zv`TpmZ)vY@BkAan*zEy(s`*{-@U z;$WPjoNx~m?`6Z;^O=K3SBL3LrIxfU{&g)edERkPQZK!mVYU-zHuV0ENDq^e<-?^U zGyRcrPDZZw*wxK(1SPUR$0t0Wc^*u_gb*>qEOP102FX|`^U%n*7z=wM@pOmYa6Z=-)T%!{tAFELY2`dTl3$&w! z7sgKXCTU(h3+8)H#Qov19%85Xo+oQh?C-q0zaM_X2twSCz|j_u!te3J2zLV#Ut_q7 zl+5LGx#{I`(9FzE$0==km|?%m?g~HB#BSz2vHynf1x14mEX^~pej*dhzD|6gMgOJ_ z8F_<>&OIz;`NSqrel?HI-K(|ypxwz}NtX!CF3&T(CkuYOnKS&%lUSU44KsgS`L>!w zl{MoT4`t=+p8>@88)Ea%*hOIkxt#b4RfrwRMr91UF_Ic~kV;|+dRW0a8Vl725+gsvtHr5 z>?3fai&9NmU|3;-nAu8OB|<(-2Kfub4MX&1i}dDd=R~Dk=U-Vr=@&lfEIYU~xtHHO z4TKt=wze`qm=69lD)sOOkZ;$9=0B#*g@X6xPM-%zG*rCXkN%eRDEUp$gAaEd29t&T zRTAg##Sk+TAYaa(LyTD__zL3?Z+45^+1o}(&f<~lQ*-z7`Um^>v@PKqOunTE#OyKFY^q&L^fqZgplhXQ>P3?BMaq6%rO5hfsiln7TppJ z>nG9|2MmL|lShn4-yz0qH>+o;Fe`V!-e*R0M|q~31B=EC$(bQZTW^!PrHCPE4i|>e zyAFK!@P}u>@hqwf%<#uv*jen5xEL|v!VQEK!F`SIz_H8emZfn#Hg}}@SuqPv+gJ@- zf3a`DT_Q#)DnHv+XVXX`H}At zmQwW2K`t@(k%ULJrBe6ln9|W8+3B*pJ#-^9P?21%mOk(W1{t#h?|j0ZrRi_dwGh#*eBd?fy(UBXWqAt5I@L3=@QdaiK`B_NQ$ zLXzm{0#6zh2^M zfu>HFK^d`&v|x&xxa&M|pr))A4)gFw<_X@eN`B1X%C^a{$39fq`(mOG!~22h)DYut z(?MONP1>xp4@dIN^rxtMp&a^yeGc8gmcajyuXhgaB;3}vFCQFa!pTDht9ld9`&ql`2&(dwNl5FZqedD^BP zf5K1`(_&i7x-&rD=^zkFD87idQrk(Y?E;-j^DMCht`A8Qa5J-46@G_*Y3J+&l{$}*QCATEc9zuzaQGHR8B;y*>eWuv)E##?Ba3w= zZ|v(l{EB`XzD#|ncVm#Wy?#Nzm3bS1!FJ70e{DGe$EgNDg7<_ic^mJSh&Xc|aTwCrTv;XkW~UlS&G%KyLklCn}F^i(YP(f z{cqH%5q9ND_S;l$HRP$Q@`D=F*_1$CXIA5X@|V&Vir$NQ$vCx!b&LGCR<-2y)m%HI zxeeyQIjiWcf4uD9+FP+EJ`&$oJ%$R(#w~GjqP|aTQj#d(;l#rq$vcM&Y4ZQ_i{Kpx z?k2BtoKb?+1-EVmG^ne-W%8+y?i#J5N5g8f^qpH5(ZZp7$u+?I9GB+&MREX?TmVV$ zA}Ps=^CkD^sD9N;tNtN!a>@D^&940cTETu*DUZlJO*z7BBy`Rl;$-D@8$6PFq@tz0 z=_2JMmq-JRSvx`;!XM|kO!|DENI-5ke8WR*Zj#vy#Nf1;mW-{6>_sCO8?sVWOKDM| zR(iaZrBrzlRatUzp_Y|2nOXnY2G%WLGXCo9*)th_RnXvXV=q;WNAimI98!A54|$&OCCG%$4m{%E&o?S|Qx<4K~YGmM1CS!vZAzLN%d znbZsw6ql=XkiwSbNofNeA42q8#LH6Rk(u@z172O#6K>Sb{#`t#GUgpd{2;D(9@I_9 zwsY(6Go7RmOThs2rM3|Z#Vbs}CHPLgBK6gE8;XkJQDx~p5wJ?XkE(0<^hwnt6;$~R zXCAzMfK@`myzdkkpv*ZbarVwCi&{-O#rswrb-#x4zRkxfVCq;mJLic|*C92T?0CYv z)FCqY$xA(QZmggPocZqQj0Rc?=Afna`@fpSn)&nSqtI}?;cLphqEF3F9^OZfW9@HDunc^2{_H)1D9(O}4e zJMi_4(&$CD{Jf5&u|7#Iq*F~)l!8pAzNrX^<&wfEu~}Ipslzx=g^ff2?B9SnV=!$ zv&K0`hMN6BVIusHNX-lr`#K?OG1S*S4rCQaI3ea(!gCl7YjxJ3YQ)7-b&N*D8k><*x|47s3; z4f~WTWuk|Qd*d*DICV}Vb0YSzFZp5|%s4}@jvtTfm&`|(jNpajge zD}@CMaUBs+b?Yu6&c#18=TxzMCLE76#Dy=DLiq_a_knQX4Uxk$&@3ORoBFK_&a>`QKaWu^)Hzrqz{5)?h3B_`4AOn{fG9k zEwnjQb>8XRq!k?rmCd6E**1cY#b9yczN4mD%GLCeRk}{TmR1*!dTNzY;(f!B0yVuk zSjRyf;9i@2>bdGSZJ=FNrnxOExb075;gB z*7&YR|4ZraFO#45-4h%8z8U}jdt?83AmU3)Ln#m3GT!@hYdzqqDrkeHW zU#R`Z8RHq996HR=mC}SRGtsz07;-C-!n*ALpwwBe~loM)YqMH)Um$sH0RbTTzxFd)h1=-w5Yl3k|3nQ zZG>=_yZ7Lsn=b8_MZI+LSHLGYSSCc?ht~7cv#39>Moz6AS}5 zus?xge0PGdFd2FpXgIscWOyG}oxATgd$yl0Ugf_&J_vwt`)XWx!p*gE_cWU(tUTnz zQS}!bMxJyi3KWh^W9m zxLcy``V@EfJzYjK@$e7Yk=q!kL8cd3E-zpc*wwvGJ62O!V;N zFG7Y?sJ+^a%H1;rdDZRu2JmGn6<&ERKes=Pwx)GG-nt73&M78+>SOy!^#=gvLB)2H zjv!J0O`-zft|0Jv$3k5wScY)XB+9leZgR5%3~HtZA=bCg7=Dn+F}>2lf;!*1+vBtf z9jhmqlH=t5XW{0MC7Y~O7jaju&2`p!ZDLGlgnd~%+EJ%A#pIByi-+EOmoLVoK&ow8 zTDjB%0hxhiRv+O3c2*y00rMA=)s|3-ev7emcbT43#izku7dvaDXy1IMV0ahjB9yzi z9C9fN+I2Mzt1*{`a6B?+PdWHiJ5fH}rb2t>q)~3RfCxmyK^y5jN7Pn(9DFh61GO%p zuBErj=m|bDn_L8SINU)Z&@K*AgGz+SUYO_RUeJt=E0M+eh&kqK;%Y1psBNU<4-s9# ziHFr7QP6Ew=-2CdfA#Bf|EsctH;<&=Hsd>)Ma8NvHB$cpVY@}TV!UN}3?9o@CS5kw zx%nXo%y|r5`YOWoZi#hE(3+rNKLZ2g5^(%Z99nSVt$2TeU2zD%$Q(=$Y;%@QyT5Rq zRI#b><}zztscQaTiFbsu2+%O~sd`L+oKYy5nkF4Co6p88i0pmJN9In`zg*Q;&u#uK zj#>lsuWWH14-2iG z&4w{6QN8h$(MWPNu84w1m{Qg0I31ra?jdyea*I~Xk(+A5bz{x%7+IL}vFDUI-Rf{! zE^&Dau9QxA2~)M98b42(D6Q}2PUum0%g>B?JS?o~VrP+Go2&c-7hIf7(@o1*7k$zS zy@o5MEe8DoX$Ie(%SZByyf9Xf9n8xkoX}s6RiO1sg*kAV^6EAAz$>*x^OmIy!*?1k zG+UQ|aIWDEl%)#;k{>-(w9UE7oKM#2AvQud}sby=D7$l6{$}SE8O9WgHM_+ zJ?tHeu@Pi93{AuwVF^)N(B~0?#V*6z;zY)wtgqF7Nx7?YQdD^s+f8T0_;mFV9r<+C z4^NloIJIir%}ptEpDk!z`l+B z5h(k$0bO$VV(i$E@(ngVG^YAjdieHWwMrz6DvNGM*ydHGU#ZG{HG5YGTT&SIqub@) z=U)hR_)Q@#!jck+V`$X5itp9&PGiENo(yT5>4erS<|Rh#mbCA^aO2rw+~zR&2N6XP z5qAf^((HYO2QQQu2j9fSF)#rRAwpbp+o=X>au|J5^|S@(vqun`du;1_h-jxJU-%v| z_#Q!izX;$3%BBE8Exh3ojXC?$Rr6>dqXlxIGF?_uY^Z#INySnWam=5dV`v_un`=G*{f$51(G`PfGDBJNJfg1NRT2&6E^sG%z8wZyv|Yuj z%#)h~7jGEI^U&-1KvyxIbHt2%zb|fa(H0~Qwk7ED&KqA~VpFtQETD^AmmBo54RUhi z=^Xv>^3L^O8~HO`J_!mg4l1g?lLNL$*oc}}QDeh!w@;zex zHglJ-w>6cqx3_lvZ_R#`^19smw-*WwsavG~LZUP@suUGz;~@Cj9E@nbfdH{iqCg>! zD7hy1?>dr^ynOw|2(VHK-*e%fvU0AoKxsmReM7Uy{qqUVvrYc5Z#FK&Z*XwMNJ$TJ zW1T**U1Vfvq1411ol1R?nE)y%NpR?4lVjqZL`J}EWT0m7r>U{2BYRVVzAQamN#wiT zu*A`FGaD=fz|{ahqurK^jCapFS^2e>!6hSQTh87V=OjzVZ}ShM3vHX+5IY{f^_uFp zIpKBGq)ildb_?#fzJWy)MLn#ov|SvVOA&2|y;{s;Ym4#as?M^K}L_g zDkd`3GR+CuH0_$s*Lm6j)6@N;L7Vo@R=W3~a<#VxAmM&W33LiEioyyVpsrtMBbON+ zX^#%iKHM;ueExK@|t3fX`R+vO(C zucU#Xf>OjSH0Kd%521=Sz%5Y!O(ug(?gRH@K>IUayFU~ntx`Wdm27dB-2s@)J=jf_ zjI-o;hKnjQ|Lg~GKX!*OHB69xvuDU zuG-H48~inKa)^r539a{F)OS`*4GShX>%BR)LU~a-|6+sx&FYsrS1}_b)xSNOzH|Kv zq>+1-cSc0`99EsUz(XWcoRO)|shn>TqKoQBHE)w8i8K`*Xy6(ls%WN_#d}YC^)NJ; zzl8!Zduz^Gg8*f0tCWnLEzw6k5Fv!QWC1x4)3r}+x~@#O8_)0>lP-@3(kFwLl%%Mz(TpATVnL5Pl2Gahw45QXI~>Hrw))CcEs@PP?}4^zkM$ z@(?H6^`Jl?A=(&Ue;W0`*a8&fR7vde@^q^AzX^H#gd~96`Ay^_A%?;?@q@t7l7iGn zWms#2J|To4;o1?3g3L!K_chdtmbEg~>U>$5{WO@Ip~YE&H($(^X6y_OBuNHkd0wu= z4rXGy#-@vZ?>M<_gpE8+W-{#ZJeAfgE#yIDSS?M?K(oY@A|FaS3P;OjMNOG% zGWyZWS(}LJCPaGi9=5b%sq$i!6x@o(G}wwfpI5|yJe24d_V}cT1{^(Qe$KEMZ;>I@ zuE6ee%FLgem>CKEN8SeY)fpK#>*lGcH~71)T4p|9jWT;vwM@N!gL}nCW=Oi6+_>K2 zl4sWXeM1U}RETA~hp=o3tCk+?Zwl#*QA>Wwd|FlUF0)U;rEGPD1s0Syluo zfW9L(F>q9li8YKwKXZrp*t)N9E;?&Hdbm-AZp2BcDTHO6q=tzVkZsozEIXjIH`tm} zo2-UleNm*Lj7zgvhBph_|1IggkSuW~S(9ueZEfao8BuzqlF(a+pRivTv(Zb zXFaHwcuovdM#d+!rjV7F<^VW&@}=5|xj!OUF)s0zh|8yzC)7!9CZB+TLnycoGBsDF z$u&j={5c(4A$iik;x6_S96Krw8--+9pGY+*oSVTIuq;$z8*)W8B~rMX_(U6uM}!Gc`T;WfEKwI84%)-e7j}>NA(O_)3Vn9 zjXxY1Fnx3Fx%CFpUHVu0xjvxgZv}F9@!vC!lD|05#ew3eJ}@!V&urwRKH`1f{0e^o zWvM1S@NbI6pHdzm33pza_q;#?s%J*$4>10uYi4l%5qi|j5qh+D=oqSJR=7QwkQh>>c$|uJ#Z@lK6PMHs@ zyvnnoOSkGQkYz#g>||xN&1fV)aJb*y--Y`UQV~lt!u8yTUG59ns1l7u>CX2F>9fl; zB)zH3z^XHmSU{F_jlvESvaNL&nj^;j)29~1LcTYw>(6}>bt0hiRooqm0@qTj%A&P9 zKmexPwyXG@Rs1i+8>AJ;=?&7RHC7Mn%nO>@+l?Qj~+lD376O2rp)>tlVHn8MKq zwop1KRLhUjZ|+6ecGIAftSPT*3i94=QzYCi_ay+5J&O(%^IsqZ!$w-^bmd7ds$^!q z;AkC;5mTAU>l0S$6NSyG30Ej?KPq@#T)^x#x?@U~fl2m$Ffk)s6u|iPr!)-j0BlA7p3E*A|My8S#KH;8i-IQq7Q*F4*ZVPe<{^SWz_ zr?!6cS+@|C#-P~d#=W1n7acn8_pg#W-lcyf+41zwR+BU6`jUkP^`*wgX)FxEaXzoi z8)?FE*97Yqz|b@fR1(r{QD363t260rQ(F||dt9^xABi+{C*_HL9Zt5T;fq|#*b}=K zo5yj_cZB(oydMAL&X(W6yKf>ui?!%(HhiHJ83EA|#k0hQ!gpVd( zVSqRR&ado+v4BP9mzamKtSsV<|0U-Fe2HP5{{x&K>NxWLIT+D^7md{%>D1Z-5lwS~ z6Q<1`Hfc+0G{4-84o-6dr@)>5;oTt|P6jt9%a43^wGCslQtONH)7QXJEYa!c~39 zWJpTL@bMYhtem1de>svLvOUa*DL7+Ah0(_~2|ng`!Z!qiN}6xL;F}<%M8qWv&52-Y zG*1A&ZKlp~{UFV%Hb_*Re({93f7W*jJZMV-Yn|<+l3SPN+%GuPl=+tSZxxr%?6SEc zntb0~hcK691wwxlQz_jSY+V_h+0o`X!Vm{;qYK$n?6ib1G{q>a%UejzOfk6q<=8oM z6Izkn2%JA2E)aRZbel(M#gI45(Fo^O=F=W26RA8Qb0X;m(IPD{^Wd|Q;#jgBg}e( z+zY(c!4nxoIWAE4H*_ReTm|0crMv8#RLSDwAv<+|fsaqT)3}g=|0_CJgxKZo7MhUiYc8Dy7B~kohCQ$O6~l#1*#v4iWZ=7AoNuXkkVVrnARx?ZW^4-%1I8 zEdG1%?@|KmyQ}tploH>5@&8Cp{`)CxVQOss&x|Z7@gGL3=tCVNDG!N9`&;N$gu^MDk|`rRm=lhnXAJ5v1T)WTz)qvz|Dw zR?{}W4VB(O6#9%o9Z^kFZZV*PDTAWqkQ8TH!rti8QIcR&>zcg3qG}&A( zwH^K8=`1C1lRfhrX{IvNn9R9!$UMC%k(;;VH%`S0h_on|Gh6qDSH&#}*m-u{;p~WB zF$_I~xx!RxVrxNQdr@3T>{F#^D{@N9OYC9LsV62F_Z1KYQ5yk*C5WQ4&q}Kz(I{9UWWf?LIcCZicB1EO_FUH*a9QKS(4IR%#D5DTi_@M}Q_-4)J4d zz@!vR0}5MPAOK(#uL+$7XOcP$5SS#*EK9Rt6XN%}HB7@`8S^gNRk!HLv(CvCjX4o= z>9scPwWbE!F8T=@x9^;s-OF2!eO(!gL9$-AmzUiDnu&QS4If5ea2T070n1-IyNhck z9$J8b!he3@q5qB-cQ;5ymVIXXn46kK0sqKZV+3s3^mac=3~BrCW})WNrrRs1KtMmg zLzwXYC?@_H#s3W4D$W0rh%WL|G<1$$uYdptPbxy0ke!c%v#x9I=2?S)YVkg1X$W^cB!i>B{e9wXlm8AcCT8|verIZQngj>{%W%~W0J%N`Q($h z^u3}p|HyHk?(ls7?R`a&&-q@R<94fI30;ImG3jARzFz<(!K|o9@lqB@Va+on`X2G) zegCM8$vvJ$kUwXlM8df|r^GQXr~2q*Zepf&Mc%kgWGTf;=Wx%7e{&KId-{G}r22lI zmq%L6Y-M*T$xf8 z#kWOBg2TF1cwcd{<$B)AZmD%h-a6>j z%I=|#ir#iEkj3t4UhHy)cRB$3-K12y!qH^1Z%g*-t;RK z6%Mjb*?GGROZSHSRVY1Ip=U_V%(GNfjnUkhk>q%&h!xjFvh69W8Mzg)7?UM=8VHS* zx|)6Ew!>6-`!L+uS+f0xLQC^brt2b(8Y9|5j=2pxHHlbdSN*J1pz(#O%z*W-5WSf# z6EW5Nh&r<;$<3o1b013?U$#Y!jXY)*QiGFt|M58sO45TBGPiHl4PKqZhJ|VRX=AOO zsFz-=3$~g#t4Ji9c;GFS9L~}~bzgCqnYuJ-60AMDdN7HZt8_$~Of{oXaD3HVn9zkH z`>#xQNe=YpWTq_LcOoy}R`L<_4il7w4)QH4rl?AUk%?fH##I>`1_mnp&=$-%SutYT zs}sSNMWo;(a&D()U$~PG0MvZ#1lmsF&^P4l_oN#_NORD-GSmR{h_NbJ^ZdY#R9#qW zKAC%V*?y~}V1Zh#d|-z1Z8sy5A+}*cOq$xk@Pn&{QffzG-9ReyPeEhqF%~Z3@|r(s z3(wA&)dV~fELW*&*=!~l9M=7wq8xE(<@)BjjN8bUiS8@N9E{wi+Dd!V1AtT;Nl}9> zTz`2ge2Jn#Dlg1kC%oFlOe<>?jYC`Asr^%i4hH;S`*qZTPRan2a9Kjj=0aq{iVi2Z z87PZt$d(LAm_{92kl+2Z%k3KGV;~gsp;C>k?gMYZrVIzaI|0D+fka9G_4v>N96*8T zI(C8bj?A7l%V&U?H_IpSeCvf7@y1e?b>G7cN382GVO0qAMQ93(T*<*9c_;%P1}x2l zi8S$s<=e_8ww%DaBAf4oIQ7}U7_48$eYpo}Fb+F|K|43IAPR1y9xbqPPg6er{I7xj|=>-c%pGBRLn1~=5KbAb1mJAx=z(loN!w{49VkEthF>*OX z)=gqXyZB5%5lIWYPWh~{!5pSt43-)-@L@x=pmiuKP-3Cwq8qSxGNwaTT4->BWEjxk zUjr)z7WrBZB5u3iV>Y_>*i~*!vRYL)iAh5hMqNzVq1eeq=&d9Ye!26jks{f~6Ru&c zg$D;^4ui#kC`rSxx`fP!zZ^6&qSneQzZRq0F*V4QvKYKB<9FC%t#)Tik%Zq*G*IOW z3*`2!4d)!3oH>GxVcXlorJDt+JnH)p{~olYBPq|>_V@8=l#(f*diW=L+%>rfWCcPQ z#H^ksQt15Z5Uc4ODq8_JwD5^H&OGqyH6E@MabJQO>s`?bqgA6}J_QpytW{2jH#eCN z8k7y*TFZ2lj2B|1CB(@QZedFfPhX|IQbKMI;$YK>9Zla0fsU7}an6(kP;sXpBWLR` zJ#z_kk!`JJC7h(1J!+G)gL2WB2&0*~Q!%s??}GH?=`hU@03xOwU} z6s7?tGySLz!%(MwxQRiF)2(vR2wQX`YB}u&I-S+RR)LQcyH407#-{*pWLJJR?X|5 zsAl2k{&0N-?JArn@)9YTo-5+gl}R~XkbZM*5AOjPrcikpE3P?p0oN^?H+5+n)}Qxe z*RQ!-eu0RxPyF8B=}xnseNpQMXFU$d^=(G%kUd&|!BHSm7bXoGR$WA+%yjuA{|S>u z?9N6JDhS+ui~rd?wY_t7`p)|qKIMM>6jz%$jv4hc_YUDjF6-%5muq|SNuoji2)|qK zNY5+oWMe+5vu{I*grk6xlVk;(J)uuy13G`VDbj(~Vz9lA)_;$aj?=-cmd#h~N0mn{ z9EIS_d4C=L3H;Pl^;vcpb&-B+)8vt%#?gn5z>#;G{1L&8u8cXJYADMUsm9>%*%)&F zsi&I{Y=VUsV82+)hdNgDWh^M7^hMs|TA0M269^|RIGfdX1MetV2z`Ycb&_Mn4iRI! zeI6O}O9mOhN6pzfs5IfMz#Gxl`C{(111okA8M4gijgb~5s7QTyh84zUiZZ^sr1^ps z1GO`$eOS@k@XP^OVH|8)n}Wx)fKHoGwL&5;W?qEf5Jdsd!3hf7L`%QNwN0gGBm^2= z@WI+qJMJG1w2AS9d@Dt$sj_P$+S2kh7+M72^SfcdBjQEtWQ5?PT&a~G9hOo6CtS>h zoghqoR;sk{X)`ZK-M|lu{M}0>Mrs^ZW@ngC?c$26_vYKDBK^n7sFiod_xV#XcPL!^ zRPyqD{w^9u{oA3y73IW0 zH;%xop$r(Q=bq=JaLT%myEKD_2&?L@s6TzsUwE#g^OkiU6{lN)(7I?%a;_%r5_^@d zS-Z)Q-2o|~?F~f`sHlhNhiZk;!CW;3Ma6{xPlBjJx8PXc!Oq{uTo$p*tyH~ka`g<` z;3?wLhLg5pfL)2bYZTd)jP%f+N7|vIi?c491#Kv57sE3fQh(ScM?+ucH2M>9Rqj?H zY^d!KezBk6rQ|p{^RNn2dRt(9)VN_j#O!3TV`AGl-@jbbBAW$!3S$LXS0xNMr}S%f z%K9x%MRp(D2uO90(0||EOzFc6DaLm((mCe9Hy2 z-59y8V)5(K^{B0>YZUyNaQD5$3q41j-eX))x+REv|TIckJ+g#DstadNn_l~%*RBSss_jV3XS&>yNBc8H2jo(lwcLz-PuYp< z7>)~}zl$Ts0+RFxnYj7-UMpmFcw_H zYrsXM>8icD)@Iauiu_(Y#~Iyl)|pj@kHkWvg2N$kGG(W>Y)nfNn%z2xvTLwk1O2GQ zb^5KAW?c%5;VM4RWBy}`JVCBFOGQWoA9|+bgn7^fY3tSk1MSZccs9&Fy6{8F>_K@? zK(z=zgmq1R#jGE^eGV`<`>SP9SEBx!_-Ao|VZq6)-rUpd^<2GgVN&uHiM{0zA9kI( z<1^1%*uE$?4mXV@?W8}fvnBOpfwCo^?(a0E402!pZi&Kd5pp$oV%2Ofx<}YC-1mynB3X|BzWC_ufrmaH1F&VrU&Gs+5>uixj*OJ*f=gs9VR8k^7HRR$Ns|DYBc*Slz>hGK5B1}U+}#j0{ohGC zE80>WClD5FP+nUS?1qa}ENOPb2`P4ccI<9j;k?hqEe|^#jE4gguHYz-$_BCovNqIb zMUrsU;Fq%n$Ku_wB{Ny>%(B&x9$pr=Anti@#U%DgKX|HzC^=21<5Fn6EKc#~g!Mcj zJrI(gW+aK+3BWVFPWEF*ntHX5;aabHqRgU-Nr2t++%JRPP7-6$XS|M8o&YSgf3a9A zLW*tSJxoe1?#T4EocApa*+1kUIgy7oA%Ig9n@)AdY%)p_FWgF-Kxx{6vta)2X1O5y z#+%KQlxETmcIz@64y`mrSk2Z17~}k1n{=>d#$AVMbp>_60Jc&$ILCg-DTN~kM8)#o$M#Fk~<10{bQ>_@gU2uZE z*eN~mqqQC*wh{CI(!xvRQ^{jyUcvE~8N)S0bMA^SK@v;b7|xUOi63X~3Qc>2UNSD1) z7moi9K3QN_iW5KmKH>1ijU41PO>BvA6f1;kL)6io%^r>?YQ#+bB;)Rzad5;{XAJGeAT#FnDV0$w2>v|JeFIB zZ>8vmz?WVs78PuCDiHfb@D0Yi;2#%){*#?bY4dpta6dSjquGLcOw?Z{nxg98mN^4* zj&^!WMUQ_zFp+}B|G0vcNsk8(2u9(LAPk5ogKt%zgQ4^1#UCd;`-W#X8v{YyQ_m9g z8`jydw>>@1J{Q*q#5^cHVA~xR9LR3Hl@^bx)`IBKmj+Gmye36;xwL0>sS|mV+$~%b zC;2wEm&Ht3#6P|2Y0XQ+5t-aI)jn{o%&ZHWvjzEtSojFgXxNKO^e(RmM`gsJ4GrR8 zKhBtBoRjnH`mD$kT;-8ttq|iw?*`7iTF_AX<^Qe3=h8L^tqz$w$#Z@Z$`C579Jeeu ztr0z~HEazU&htfG@`HW!201!N(70hCd{%~@Wv)G*uKnJZ8>hFx`9LnYs;T>8p!`5T zx#aXXU?}B{QTV_Ux(EMzDhl-a^y^f5tRU;xnOQoN)pThr4M>-HU)As8nQ34-0*sab&z<2ye-D_3m&Q`KJJ|ZEZbaDrE%j>yQ(LM#N845j zNYrP)@)md;&r5|;JA?<~l^<=F1VRGFM93c=6@MJ`tDO_7E7Ru zW{ShCijJ?yHl63Go)-YlOW2n3W*x%w||iw(Cy>@dBJHdQl){bBVg{wmRt{#oXb9kaWqe{bJPmGE$$ z_0=cmD9dVzh<8&oyM8rK9F^bufW$Bj2cFhw&f*oKKyu$H{PI=Aqe^NL6B=dkMEAk& zE3y&F=x;e|!7kMn%(UX>G!OE$Y$@UyME#d;#d+WLmm@W@y!sboiIox^DZPB|EN<>7 z57xm5YWlFUGyF|{<*;b&Cqm+|DC8{rB9R@2EFHGL^NX*l#AcDpw6}bCmhY7!(Gv{s zm^eYNvzyJLQA#GhmL*oSt^Uulb5&ZYBuGJTC>Vm9yGaZ=Vd--pMUoDRaV_^3hE9b*Pby#Ubl65U!VBm7sV}coY)m zn1Ag^jPPLT93J{wpK%>8TnkNp;=a@;`sA7{Q}JmmS1bEK5=d@hQEWl;k$9M-PYX~S zayGm;P(Wwk23}JR7XM~kNqba`6!Z+Wt2|5K>g_j3ajhR>+;HF?88GBN!P; zr6sQ8YYpn%r^gbi8yYK7qx6U5^Tf<|VfcR$jCo`$VMVh_&(9w@O?|o3eRHq*e*#P z8-==G)D?vB3Zo~b-dkx8lg0^=gn`9FUy?ZzAfWQd>>@cyqF!sHQ_S&@$r&tTB~Lxq zAjAZTK~?J{A|L3)8K>S{`Qf%131B>?<~t=w!D{;olQ>#31R#{go`a9DOy+H*q5t+; z^*Ka!r@#8tk?~tQbylaG-$n#wP2VzIm3vjrZjcmTL zl`{6mhBhMKbSWoGqi;g3z1@G0q!ib`(Zz_o8HG_*vr8U5G|vhZn26h`f~bO&)RY0; zw(CWk*a_{ji_=O9U}66lI` zCm32)SEcAo5)5k>{<8DLI@Zz)*R29BB!^wF;WZRF9sAi39BGObmZzg?$lUn6w1rYPHSB^L4^AN zLObEaUh7TXpt6)hWck#6AZV(2`lze<`urGFre|>LUF+j5;9z%=K@&BPXCM)P$>;Xc z!tRA4j0grcS%E!urO^lsH-Ey*XY4m&9lK(;gJOyKk*#l!y7$BaBC)xHc|3i~e^bpR zz5E-=BX_5n8|<6hLj(W67{mWk@Bfc){NGAX z5-O3SP^38wjh6dCEDLB#0((3`g4rl}@I(&E8V2yDB=wYhSxlxB4&!sRy>NTh#cVvv z=HyRrf9dVK&3lyXel+#=R6^hf`;lF$COPUYG)Bq4`#>p z@u%=$28dn8+?|u94l6)-ay7Z!8l*6?m}*!>#KuZ1rF??R@Zd zrRXSfn3}tyD+Z0WOeFnKEZi^!az>x zDgDtgv>Hk-xS~pZRq`cTQD(f=kMx3Mfm2AVxtR(u^#Ndd6xli@n1(c6QUgznNTseV z_AV-qpfQ0#ZIFIccG-|a+&{gSAgtYJ{5g!ane(6mLAs5z?>ajC?=-`a5p8%b*r*mOk}?)zMfus$+W~k z{Tmz9p5$wsX1@q`aNMukq-jREu;;A6?LA(kpRut+jX?Tt?}4HGQr}7>+8z4miohO2 zU4fQ?Y8ggl%cj&>+M+)TTjn8(?^%`~!oAt#ri8gIbzIig$y#d7o##077fM9sCu%N9 zOIsq4vyox6`itu*j{eOD<$gTZd-$JuyM^cM>{?v<8# zS1yN%R0zRy&>+D*Gv-&S80?JF+Y|c^^IJWDnfy06MI2{NFO-x4JXsb@3Qp;EnL!a{ zJwKwV@mO zYVGvNmeJ!;+ce+@j@oo-+`DaPJX|h@7@4BD`QEdP?NKkYzdIa3KrZt%VUSsR+{b+| zk?dSd#9NnVl?&Y$A{-OtZ>wk%mWVF5)bf`)AA2{EFapIS4jil69Xan>*J^6Juou&`oJx|7-&|@8z?$ z2V#jm!UHstCE*qM{OGtqYY8q+x%SL6&aGY!a>@d=_G~^0;+7dY9P`oJ*)67*9Kx*O zKitC5V3g5;&L-fa37?eN=;V_c^L-ph_uKv5)Q`&!Z!RPlDWA2{J%a2q@_*?-cn@bH zIt)+mA@HaJj2RV+-MNc#y#Vji*N~m!ZyrYyg-7UK4PYK4F7Y$3Y%@Lk6iPp=I96N> z!;ih(KtZMB23*v{`5cJ}^4D*P!k1&OfU&1%borv_q|7jfaV7fL+wwx8Zp*b}B_O>NRSeJeM zpvw3M`=vSYjFYQ11kx1xqOnJ@degPh&SyXnWz-l719EiW17Yo?c~Bh~;R$MOl+jzV zM1yTq-1**x-=AVR;p0;IPi`#=E!G5qIT>EFE`Bn<7o*8!aVd7?(CZT=U9^Gi3rmWUQG z0|GaP9s$^4t_oLCs!fInyCoB(d?=tZ%%Bb2Y+X&7gvQ6~C4kU%e$W_H;-%XSM;&*HYYnLI z>%{5x_RtSUC~PI4C0H^>O%FixKYVubA>#72wexd}Cgwuw5ZYTvcN2ywVP(dO=5975 zCjo)mOa2Bo&ucEsaq8wi1{h*brT(H=XrTOy*P>?0%VV1QDr09X+Je!T)JT`02?gjX zT@B8}h|;4lH35Guq2gKZT?ags-~Ts~S=poPnQ_T1*?U|{$jaur_PjQ6WmF_(XLFG)d#|iiBC=&B zp}1eOQvQ!3UpL?K`=8hAzMkv#a^COr`J8i}d!BPX&*xp-LL#qse~mOtxI-}{yPRNV zJNTL1{7A55F~K>0e&Os%MwQ~?n1>QV=j!8o_`^-&*E|Q-L9DNr%#6sw8kQVE3E|*}$aAoO$@27ei1w=+zU%?AA!;mf#!%IV*w_D=u516!Kz1F0-WnyVB`I6F1Pc3r1=0iT<_(pCyk>@22z1$w$@M>7AIuk6+ zRG&MFVQ_7>5DLoR5HeOa$?2SA(v2u!#8;5I(ss%=x9U#R zU62n~&)22RTTsp${}6C&$+l&0skFVX%ACgc$(iQ#DVRRz!`Y+b>E?;ib(TH#6Wa=} zs(q_;SA|fhyEo7Ix%rAY9j=Ul^Rzd`3ABf+yO@~h@Rh=wo`?;8PdHE1AUo34r7izy znAr`;VavQueSu7bD5r^nXTERcW(P-{2SOSfF1x0cW1Nczvj0}@!!upORN1%_-b2bh zGt#zokJz&SveJRzlUK4DruxR(YuHEAmB%F}buU`*pAzJ7Mbgs4sg;H@&6x*wxvGm6 z>KH@ilsvvdl@CGfm4T+$agodrB=md8ygG!|O=r@FY>S_zX%*)mqf?XBX*chhQ9uPP z-(T(24)})vWD*{bQM5_hy3CD8C>anuNtCXMkG7T?Yew^>=PK!~Hlr0{-0h0cNAJ8> zRMzLFz7aJv)Yh)_s)^L&L*nDV@qfeg>_<`z1z(?s}}3tE4h|7_taB> zPfmmOCFZ8%>`gyf1@|7t3;e~mwBRCDDw(Rrt>@O}obs#1?!W((+9>d$b7t!{&wR!P ziQbn0@j=&sw={`s##Uc@uS^(tbShjtsk=qrU1LW0lu}BplIfzv{fwxNsSaG~b|ryo zTQ}YXfp6o?^sSHW>s~m;l@h6wFbIPw{Z(IqO1u){{hEZgrTdF0o$n;hYIm`h5ejym zWt^w~#8p1J)FtfY6LvGmNQ~#n>4#mN4B^ zjrQk)Zt%k}GBRD>l`<~og6N_{6HYKDtsAtd%y?KbXCQR(sW8O(v_)kwYMz|(OW zsFz6A1^abSklOl`wLC-KYI8x=oMD^qZBs}}JVW@YY|3&k&IZ_n2Ia@5WiK>buV!E- zOsYcS4dFPE7vzj%_?5i2!XY`TiPd*jy>#C`i^XG8h?f35`=)s`0EhQBN!+YrXbpt( z-bwg_Jen`w<+6&B`hldU%rr&Xdgtze>rKuJ61AI12ja-eDZZX-+u1H>Sa|7pCine9 z&MEhmT7nq`P!pPK>l?I8cjuPpN<7(hqH~beChC*YMR+p;;@6#0j2k$=onUM`IXW3> z`dtX8`|@P|Ep-_0>)@&7@aLeg$jOd4G`eIW=^dQQ*^cgKeWAsSHOY?WEOsrtnG|^yeQ3lSd`pKAR}kzgIiEk@OvQb>DS*pGidh`E=BHYepHXbV)SV6pE2dx6 zkND~nK}2qjDVX3Z`H;2~lUvar>zT7u%x8LZa&rp7YH@n@GqQ65Cv+pkxI1OU6(g`b z?>)NcE7>j@p>V0mFk-5Rpi`W}oQ!tUU&Yn8m0OWYFj|~`?aVFOx;e`M)Q!YSokY)3 zV6l-;hK6?j=mp2#1e5cCn7P6n_7)n^+MdRw@5pvkOA>|&B8`QZ32|ynqaf}Kcdro= zzQchCYM0^)7$;m2iZnMbE$!}hwk&AVvN`iX3A9mB&`*BDmLV-m`OMvd`sJ?;%U`p~ zmwow{y6sPbcZNQPZ#GQS0&mzy?s%>_p>ZM|sCXVAUlST;rQ-3#Iu!-bpFSV4g7?-l zGfX>Z#hR+i;9B};^CO@7<<#MGFeY)SC&;a{!` zf;yaQo%{bjSa8KT~@?O$cK z(DGnm7w>cG1hH#*J%X}%Y%~+nLT*{aP08@l&Nu}>!-j|!8lSqt_xUNF+Y}SQmupyb zPua2PI;@1YaIsRF*knA^rJv84Tc=7?J2}!1kMfHSO$d$+PK*u?OI%=P7;`PHxMB0k zau~T0Wk)rPEGJ$NiXW~kfPA#m%Sr|7=$tHelF9A6rFLa$^g{6)8GSW*6}#~Zb^qk% zg=pLwC!SkY+&Gne((9`TCy`i`a#eCS{A2yMi>J>p*NS*!V~aAgK;wnSOHPULqzyj- z-q4BPXqXn))iRnMF*WZj17wUYjC!h43tI7uScHLf1|WJfA7^5O9`%lH>ga`cmpiz( zs|I8nTUD4?d{CQ-vwD!2uwGU_Ts&{1_mvqY`@A{j^b?n&WbPhb418NY1*Otz19`1w zc9rn?0e_*En&8?OWii89x+jaqRVzlL!QUCg^qU&+WERycV&1+fcsJ%ExEPjiQWRTU zCJpu*1dXyvrJJcH`+OKn7;q`X#@Gmy3U?5ZAV~mXjQhBJOCMw>o@2kznF>*?qOW;D z6!GTcM)P-OY-R`Yd>FeX%UyL%dY%~#^Yl!c42;**WqdGtGwTfB9{2mf2h@#M8YyY+!Q(4}X^+V#r zcZXYE$-hJyYzq%>$)k8vSQU` zIpxU*yy~naYp=IocRp5no^PeFROluibl( zmaKkWgSWZHn(`V_&?hM{%xl3TBWCcr59WlX6Q{j45)`A^-kUv4!qM=OdcwpsGB)l} z&-_U+8S8bQ!RDc&Y3~?w5NwLNstoUYqPYs(y+lj!HFqIZ7FA>WsxAE7vB=20K zn_&y{2)Uaw4b^NCFNhJXd&XrhA4E~zD7Ue7X^f98=&5!wn_r=6qAwDkd>g#2+*ahd zaV|_P_8e%jiHh7W;cl(d=&-r-C}_Ov?bts8s^rKUWQ|XkuW!ToSwe}Z{4|kl+q&&W zn%iW48c5*ft#*m)+xSps+j(B5bPh&u0&m6=@WgwBf_QfJJzg2Qdz89HwcV`5kZ#5z zw;W&H8>5R(>KRwvd0gh30wJHA>|2N(im;~wy1HTv_}Ue%qb)>5qL^$hIyPvoT(nk_<`7F;#nS8;q!cqKspvBc<%xMsQj*h|>`Z)F6LDxue@to))OIbs2X+zY2L9#2UNrR^)?c8&PFc?j*&Q-r|C%7a$)ZRQ->#|?rEj&M4spQfNt;J^ntwf(d+q;tt)C`d{*|t)czD4x-qw{Chm0vuKp8axqy5`Yz z1756|;JX1q(lEieR=uT;%havqflgv+`5i!Z`R}(JNV~&`x}I9Lmm;aB7Bnc^UC?>W zu)(J7@fs}pL=Y-4aLq&Z*lO$e^0(bOW z3gWbcvb^gjEfhV=6Lgu2aX{(zjq|NH*fSgm&kBj?6dFqD2MWk5@eHt@_&^ZTX$b?o}S<9BGaCZIm6Hz)Qkruacn!qv*>La|#%j*XFp(*;&v3h4 zcjPbZWzv|cOypb@XDnd}g%(@f7A>w2Nseo|{KdeVQu)mN=W=Q`N?ID%J_SXUr0Rl# z3X;tO*^?41^%c!H;ia@hX``kWS3TR|CJ4_9j-?l6RjC=n?}r&sr>m%58&~?$JJV6{ zDq5h#m4S_BPiibQQaPGg6LIHVCc`9w3^3ZVWP$n>p7 z5dIEH-W9e;$Id8>9?wh%WnWf>4^1U<%vn=<4oNFhVl9zVk+jn;WtQUQ)ZeEjKYy8C z3g#tIb28thR1nZdKrN}(r zJdy-Y3Rvr5D3D|msZbmE;FLePbiM0ZjwTIQQHk)8G+sB$iwmEa2kQv&9Vs9m#$_8j zNKz}(x$Wc(M)a9H-Pn?5(Lk-CmOS(&+EVLOfsiq>e3ru6P?Lp>FOwPt>0o=j8UyF^ zO{(vf#MGx^y~WaOKnt%I78s}60(O#jFx0^47^Ikh$QTar(Dg$c=0KR|rRD|6s zz?tEX0_=(Hm0jWl;QOu!-k)mV?^i(Etl=Lg-{ z0G}CBprLX60zgAUz-fS^&m#o;erEC5TU+mn_Wj(zL$zqMo!e`D>s7X&;E zFz}}}puI+c%xq0uTpWS3RBlIS2jH0)W(9FU1>6PLcj|6O>=y)l`*%P`6K4}U2p}a0 zvInj%$AmqzkNLy%azH|_f7x$lYxSG=-;7BViUN(&0HPUobDixM1RVBzWhv8LokKI2 zjDwvWu=S~8We)+K{oMd-_cuXNO&+{eUaA8Ope3MxME0?PD+0a)99N>WZ66*;sn(N++hjPyz5z0RC{- z$pcSs{|)~a_h?w)y}42A6fg|nRnYUjMaBqg=68&_K%h3eboQ=%i083nfIVZZ04qOp%d*)*hNJA_foPjiW z$1r8ZZiRSvJT3zhK>iR@8_+TTJ!tlNLdL`e0=yjzv3Ie80h#wSfS3$>DB!!@JHxNd z0Mvd0Vqq!zfDy$?goY+|h!e(n3{J2;Ag=b)eLq{F0W*O?j&@|882U5?hUVIw_v3aV8tMn`8jPa5pSxzaZe{z}z|}$zM$o=3-mQ0Zgd?ZtaI> zQVHP1W3v1lbw>|?z@2MO(Ex!5KybKQ@+JRAg1>nzpP-!@3!th3rV=o?eiZ~fQRWy_ zfA!U9^bUL+z_$VJI=ic;{epla<&J@W-QMPZm^kTQ8a^2TX^TDpza*^tOu!WZ=T!PT z+0lJ*HuRnNGobNk0PbPT?i;^h{&0u+-fejISNv#9&j~Ep2;dYspntgzwR6<$@0dTQ z!qLe3Ztc=Ozy!btCcx!G$U7FlBRe}-L(E|RpH%_gt4m_LJllX3!iRYJEPvxcJ>C76 zfBy0_zKaYn{3yG6@;}S&+BeJk5X}$Kchp<Ea-=>VDg&zi*8xM0-ya!{ zcDN@>%H#vMwugU&1KN9pqA6-?Q8N@Dz?VlJ3IDfz#i#_RxgQS*>K+|Q@bek+s7#Qk z(5NZ-4xs&$j)X=@(1(hLn)vPj&pP>Nyu)emQ1MW6)g0hqXa5oJ_slh@(5MMS4xnG= z{0aK#F@_p=e}FdAa3tEl!|+j?h8h`t0CvCmNU%dOwEq<+jmm-=n|r|G^7QX4N4o(v zPU!%%w(Cet)Zev3QA?;TMm_aEK!5(~Nc6pJlp|sQP@z%JI}f0_`u+rc`1Df^j0G&s ScNgau(U?ep-K_E5zy1%ZQTdPn 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 From bedd6f49a2e7e7970c2616fbbe82d399d3ad69b3 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 11 Oct 2024 17:04:55 +0000 Subject: [PATCH 21/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe71941..92bda4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.1.0-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.0-beta) (2024-10-11) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.1-beta...v5.1.0-beta) + +**Merged pull requests:** + +- chore\(next\): Create shared starter [\#124](https://github.com/reactive-commons/reactive-commons-java/pull/124) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.0.1-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.1-beta) (2024-09-02) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.5...v5.0.1-beta) diff --git a/gradle.properties b/gradle.properties index d6a490b4..2427c03b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.0.1-betaLOCAL +version=5.1.0-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,async-commons-starter onlyUpdater=true \ No newline at end of file From cee535cad37d536fcd47dfcd5b4b567f3e95b2fc Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:40:57 -0500 Subject: [PATCH 22/50] docs(scenarios): Add scenarios documentation (#125) * docs(scenarios): Add some documentation for multi-broker support --- async/async-commons/async-commons.gradle | 2 +- async/async-kafka/async-kafka.gradle | 3 +- async/async-rabbit/async-rabbit.gradle | 4 +- build.gradle | 6 +- .../reactive-commons/1-getting-started.md | 20 +- .../11-creating-a-cloud-event.md | 53 +- .../reactive-commons/3-sending-a-command.md | 5 +- .../4-making-an-async-query.md | 3 + .../reactive-commons/5-handler-registry.md | 28 +- .../9-configuration-properties.md | 2 +- docs/docs/scenarios/1-single-broker.md | 180 + .../docs/scenarios/2-two-brokers-same-type.md | 266 + docs/docs/scenarios/_category_.json | 8 + docs/package-lock.json | 6267 +++++++++++------ docs/package.json | 13 +- docs/src/components/ThemeImage/index.js | 32 + docs/static/img/scenarios/1-scenario-dark.svg | 10 + docs/static/img/scenarios/1-scenario.svg | 10 + docs/static/img/scenarios/2-scenario-dark.svg | 11 + docs/static/img/scenarios/2-scenario.svg | 11 + main.gradle | 4 +- .../async-kafka-sender-client.gradle | 1 + .../java/sample/SampleRestController.java | 3 + .../starter/config/ReactiveCommonsConfig.java | 2 +- .../health/ReactiveCommonsHealthConfig.java | 2 +- 25 files changed, 4858 insertions(+), 2088 deletions(-) create mode 100644 docs/docs/scenarios/1-single-broker.md create mode 100644 docs/docs/scenarios/2-two-brokers-same-type.md create mode 100644 docs/docs/scenarios/_category_.json create mode 100644 docs/src/components/ThemeImage/index.js create mode 100644 docs/static/img/scenarios/1-scenario-dark.svg create mode 100644 docs/static/img/scenarios/1-scenario.svg create mode 100644 docs/static/img/scenarios/2-scenario-dark.svg create mode 100644 docs/static/img/scenarios/2-scenario.svg diff --git a/async/async-commons/async-commons.gradle b/async/async-commons/async-commons.gradle index 58d5e99b..ce803b55 100644 --- a/async/async-commons/async-commons.gradle +++ b/async/async-commons/async-commons.gradle @@ -10,7 +10,7 @@ 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.17.0' + implementation 'commons-io:commons-io:2.18.0' implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' testImplementation 'io.projectreactor:reactor-test' diff --git a/async/async-kafka/async-kafka.gradle b/async/async-kafka/async-kafka.gradle index a24b92b5..6f576f27 100644 --- a/async/async-kafka/async-kafka.gradle +++ b/async/async-kafka/async-kafka.gradle @@ -8,5 +8,6 @@ dependencies { api project(':domain-events-api') api project(':async-commons') api 'io.projectreactor.kafka:reactor-kafka:1.3.23' - api 'io.cloudevents:cloudevents-json-jackson:4.0.1' + + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' } \ No newline at end of file diff --git a/async/async-rabbit/async-rabbit.gradle b/async/async-rabbit/async-rabbit.gradle index db5ddce6..d5da11f9 100644 --- a/async/async-rabbit/async-rabbit.gradle +++ b/async/async-rabbit/async-rabbit.gradle @@ -13,6 +13,8 @@ dependencies { api 'io.projectreactor.rabbitmq:reactor-rabbitmq:1.5.6' api 'com.rabbitmq:amqp-client' api 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'io.projectreactor:reactor-test' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' + + testImplementation 'io.projectreactor:reactor-test' } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 67426f4e..1bf774f3 100644 --- a/build.gradle +++ b/build.gradle @@ -12,10 +12,10 @@ buildscript { plugins { id 'jacoco' - id 'org.sonarqube' version '5.1.0.4882' - id 'org.springframework.boot' version '3.3.4' apply false + id 'org.sonarqube' version '6.0.0.5145' + id 'org.springframework.boot' version '3.4.0' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.17.26' + id 'co.com.bancolombia.cleanArchitecture' version '3.20.2' } repositories { diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index 36fa4c10..d8985c64 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -50,6 +50,16 @@ dependencies { } ``` +Note: If you will use Cloud Events, you should include the Cloud Events dependency: + +```groovy +dependencies { + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} +``` + +```groovy + ### Configuration properties Also you need to include the name for your app in the `application.properties`, it is important because this value will @@ -83,7 +93,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.standalone.config.RabbitProperties" +```java title="org.reactivecommons.async.rabbit.config.RabbitProperties" @Configuration public class MyRabbitMQConfig { @@ -206,6 +216,14 @@ dependencies { } ``` +Note: If you will use Cloud Events, you should include the Cloud Events dependency: + +```groovy +dependencies { + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} +``` + ### Configuration properties Also you need to include the name for your app in the `application.properties`, it is important because this value will diff --git a/docs/docs/reactive-commons/11-creating-a-cloud-event.md b/docs/docs/reactive-commons/11-creating-a-cloud-event.md index 1db4d460..30296a20 100644 --- a/docs/docs/reactive-commons/11-creating-a-cloud-event.md +++ b/docs/docs/reactive-commons/11-creating-a-cloud-event.md @@ -16,8 +16,19 @@ In order to instantiate a CloudEvent you may need to include the dependencies: ```groovy implementation 'io.cloudevents:cloudevents-core:' +// or +implementation 'io.cloudevents:cloudevents-json-jackson:' +``` + +## Creating a CloudEvent instance with our Data wrapper + +add this classes: + +```java +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.CloudEvent; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; ``` -## Creating a CloudEvent instance ```java CloudEvent commandCloudEvent = CloudEventBuilder.v1() @@ -44,4 +55,44 @@ CloudEvent eventCloudEvent = CloudEventBuilder.v1() .withTime(OffsetDateTime.now()) .withData("application/json", CloudEventBuilderExt.asCloudEventData(eventData)) // eventData is your own object .build(); +``` + +## Creating a CloudEvent instance with jackson wrapper Data wrapper + +add this classes: + +```java +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.CloudEvent; +import io.cloudevents.jackson.JsonCloudEventData; +import com.fasterxml.jackson.databind.ObjectMapper; +``` + +```java +ObjectMapper mapper = new ObjectMapper(); // You should convert your object to a JsonNode + +CloudEvent commandCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.command.name") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(mapper.valueToTree(commandData))) // commandData is your own object + .build(); + +CloudEvent queryCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.query.name") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(mapper.valueToTree(queryData))) // queryData is your own object + .build(); + +CloudEvent eventCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.event.name") + .withDataContentType("application/json") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(mapper.valueToTree(eventData))) // eventData is your own object + .build(); ``` \ No newline at end of file diff --git a/docs/docs/reactive-commons/3-sending-a-command.md b/docs/docs/reactive-commons/3-sending-a-command.md index 295f0b50..62142820 100644 --- a/docs/docs/reactive-commons/3-sending-a-command.md +++ b/docs/docs/reactive-commons/3-sending-a-command.md @@ -39,6 +39,9 @@ public interface DirectAsyncGateway { } ``` +You can send a CloudEvent or a Command\ to a target application. You also can send a command to a specific domain +(remote broker out of you application context). + ## Enabling autoconfiguration To send Commands you should enable the respecting spring boot autoconfiguration using the `@EnableDomainEventBus` annotation @@ -50,7 +53,7 @@ For example: public class ReactiveDirectAsyncGateway { public static final String TARGET_NAME = "other-app";// refers to remote spring.application.name property public static final String SOME_COMMAND_NAME = "some.command.name"; - private final DirectAsyncGateway gateway; // Auto injectec bean created by the @EnableDirectAsyncGateway annotation + private final DirectAsyncGateway gateway; // Auto injected bean created by the @EnableDirectAsyncGateway annotation public Mono runRemoteJob(Object command/*change for proper model*/) { return gateway.sendCommand(new Command<>(SOME_COMMAND_NAME, UUID.randomUUID().toString(), command), TARGET_NAME); diff --git a/docs/docs/reactive-commons/4-making-an-async-query.md b/docs/docs/reactive-commons/4-making-an-async-query.md index 21b24159..6c1d15f5 100644 --- a/docs/docs/reactive-commons/4-making-an-async-query.md +++ b/docs/docs/reactive-commons/4-making-an-async-query.md @@ -38,6 +38,9 @@ public interface DirectAsyncGateway { In this method the Class\ called type is the return type of the query, represented by a JSON Serializable object +You can send a CloudEvent or an AsyncQuery\ to a target application. You also can send a query to a specific domain +(remote broker out of you application context). + ## Enabling autoconfiguration To do an Async Query you should enable the respecting spring boot autoconfiguration using the `@EnableDirectAsyncGateway` annotation diff --git a/docs/docs/reactive-commons/5-handler-registry.md b/docs/docs/reactive-commons/5-handler-registry.md index 207a3997..79818a90 100644 --- a/docs/docs/reactive-commons/5-handler-registry.md +++ b/docs/docs/reactive-commons/5-handler-registry.md @@ -16,12 +16,24 @@ The next methods are the main methods that you can use to register a handler. ```java public class HandlerRegistry { - public HandlerRegistry listenEvent(String eventName, EventHandler handler, Class eventClass){...} - public HandlerRegistry handleDynamicEvents(String eventNamePattern, EventHandler handler, Class eventClass){...} - public HandlerRegistry listenNotificationEvent(String eventName, EventHandler handler, Class eventClass){...} - public HandlerRegistry handleCommand(String commandName, CommandHandler fn, Class commandClass){...} - public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass){...} - public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) {...} - // ... other methods for eda variant and overloads + public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, Class eventClass) + public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, CloudEventHandler handler) + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) + public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler handler) + public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, Class eventClass) + public HandlerRegistry listenNotificationCloudEvent(String eventName, CloudEventHandler handler) + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, Class eventClass) + public HandlerRegistry handleDynamicCloudEvents(String eventNamePattern, CloudEventHandler handler) + public HandlerRegistry handleCommand(String commandName, DomainCommandHandler fn, Class commandClass) + public HandlerRegistry handleCloudEventCommand(String commandName, CloudCommandHandler handler) + public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) + public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandler handler) + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandlerDelegate handler) } -``` \ No newline at end of file +``` + +Methods that Has `CloudEvent` in the name are related to the CloudEvent specification. + +Methods that has `domain` String argument are related to the multi-broker support, this support is limited to listen events +from different domains (brokers) independent of the technology. \ No newline at end of file diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index c64b9ad4..5d55f9e1 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -67,7 +67,7 @@ You can override this settings programmatically through a `AsyncPropsDomainPrope ```java package sample; -import org.reactivecommons.async.rabbit.standalone.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.props.AsyncProps; import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; import org.springframework.context.annotation.Bean; diff --git a/docs/docs/scenarios/1-single-broker.md b/docs/docs/scenarios/1-single-broker.md new file mode 100644 index 00000000..1dbafcf7 --- /dev/null +++ b/docs/docs/scenarios/1-single-broker.md @@ -0,0 +1,180 @@ +--- +sidebar_position: 1 +--- + +# Single Broker + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ThemeImage from '../../src/components/ThemeImage'; + + + +Both apps the `App 1` and the `App 2` are connected to the same `Broker`, so both has the same connection configuration, +and `Broker` is considered the `app` domain for both apps. + + + + +You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +bean. + +```yaml +app: + async: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + listenReplies: true # if you will not use ReqReply patter 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. + 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: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + events: + exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently + eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} + notificationSuffix: notification # notification events queue name suffix + direct: + exchange: directMessages # you can change the exchange, but you should do it in all applications + querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} + commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default + discardTimeoutQueries: false # enable to discard this condition + global: + exchange: globalReply # you can change the exchange, but you should do it in all applications + repliesSuffix: replies # async query replies events queue name suffix + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / +``` + +You can override this settings programmatically through a `AsyncPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncPropsDomainProperties customDomainProperties() { + RabbitProperties propertiesApp = new RabbitProperties(); + propertiesApp.setHost("localhost"); + propertiesApp.setPort(5672); + propertiesApp.setVirtualHost("/"); + propertiesApp.setUsername("guest"); + propertiesApp.setPassword("guest"); + + return AsyncPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.SecretFiller` class. + +```java + +@Bean +@Primary +public AsyncPropsDomain.SecretFiller customFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + + You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncKafkaProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java) +bean. + +```yaml +reactive: + commons: + kafka: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + 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 + bootstrap-servers: localhost:9092 +``` + +You can override this settings programmatically through a `AsyncKafkaPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncProps; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncKafkaPropsDomainProperties customKafkaDomainProperties() { + KafkaProperties propertiesApp = new KafkaProperties(); + propertiesApp.setBootstrapServers(List.of("localhost:9092")); + + return AsyncKafkaPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncKafkaPropsDomain.KafkaSecretFiller` +class. + +```java + +@Bean +@Primary +public AsyncKafkaPropsDomain.KafkaSecretFiller customKafkaFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + \ No newline at end of file diff --git a/docs/docs/scenarios/2-two-brokers-same-type.md b/docs/docs/scenarios/2-two-brokers-same-type.md new file mode 100644 index 00000000..2874565a --- /dev/null +++ b/docs/docs/scenarios/2-two-brokers-same-type.md @@ -0,0 +1,266 @@ +--- +sidebar_position: 2 +--- + +# Two Brokers same Broker Type - Emit to external Broker + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ThemeImage from '../../src/components/ThemeImage'; + + + +`App 1` has only connection to `Broker 1`, which is considered the `app` domain for this app. This is the same as +the [Single Broker](./1-single-broker.md) scenario. + +`App 3` has only connection to `Broker 2`, which is considered the `app domain` for this app. This is the same as +the [Single Broker](./1-single-broker.md) scenario. + +`App 2` has two brokers, `Broker 1` and `Broker 2`. `Broker 1` is considered the `app` domain for this app, and `Broker 2` +is considered an external broker which will be called `accounts` domain for this scenario. + +So you need to configure the connection properties for each broker. + +In this scenario, `Broker 1` is considered the `app` domain by default, so `App 2` can listen and send all operations +from/to this broker, but it can only publish commands and queries to `Broker 2` and listen for events from the +`Broker 2` (`accounts` domain). + +Note: `App 2` cannot listen notification events, cannot listen for queries, and commands from `Broker 2`. This is for +responsibility segregation. + +To send commands and queries to `Broker 2` you need to use the `DirectAsyncGateway` interface with the `accounts` domain +, and the same when listen, you should pass the domain name. + +### Listen from external domain + +```java +@Bean +@Primary +public HandlerRegistry handlerRegistrySubs(UseCase useCase) { + return HandlerRegistry.register() + //.serveQuery(...) + //.handleCommand(...) + //.listenEvent(...) + //.listenNotificationEvent(...) + .listenDomainEvent("accounts", "event-name", handler::process, MyEventData.class) + .listenDomainCloudEvent("accounts", "event-name", handler::processCloudEvent); +} +``` + +### Send to external domain + +```java +@Service +@AllArgsConstructor +public class SampleRestController { + private final DirectAsyncGateway directAsyncGateway; + + public Mono getTeams() { + AsyncQuery query = .... + return directAsyncGateway.requestReply(query, "external-service", Teams.class, "accounts"); + } + + public Mono getTeamsCloudEvent() { + CloudEvent query = .... + return directAsyncGateway.requestReply(query, "external-service", CloudEvent.class, "accounts") + .map(...); + } +} +``` + + +Next are configurations needed to set up this scenario for `App 2`. + + + + +You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +bean. + +```yaml +app: + async: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + listenReplies: true # if you will not use ReqReply patter 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. + 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: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + events: + exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently + eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} + notificationSuffix: notification # notification events queue name suffix + direct: + exchange: directMessages # you can change the exchange, but you should do it in all applications + querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} + commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default + discardTimeoutQueries: false # enable to discard this condition + global: + exchange: globalReply # you can change the exchange, but you should do it in all applications + repliesSuffix: replies # async query replies events queue name suffix + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5673 + username: guest + password: guest + virtual-host: /accounts +``` + +You can override this settings programmatically through a `AsyncPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncPropsDomainProperties customDomainProperties() { + RabbitProperties propertiesApp = new RabbitProperties(); // this may be loaded from secrets + propertiesApp.setHost("localhost"); + propertiesApp.setPort(5672); + propertiesApp.setVirtualHost("/"); + propertiesApp.setUsername("guest"); + propertiesApp.setPassword("guest"); + + RabbitProperties propertiesAccounts = new RabbitProperties(); // this may be loaded from secrets + propertiesAccounts.setHost("localhost"); + propertiesAccounts.setPort(5673); + propertiesAccounts.setVirtualHost("/accounts"); + propertiesAccounts.setUsername("guest"); + propertiesAccounts.setPassword("guest"); + + return AsyncPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.SecretFiller` class. + +```java + +@Bean +@Primary +public AsyncPropsDomain.SecretFiller customFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + + You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncKafkaProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java) +bean. + +```yaml +reactive: + commons: + kafka: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + 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 + bootstrap-servers: localhost:9092 + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9093 +``` + +You can override this settings programmatically through a `AsyncKafkaPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncProps; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncKafkaPropsDomainProperties customKafkaDomainProperties() { + KafkaProperties propertiesApp = new KafkaProperties(); // this may be loaded from secrets + propertiesApp.setBootstrapServers(List.of("localhost:9092")); + + KafkaProperties propertiesAccounts = new KafkaProperties(); // this may be loaded from secrets + propertiesAccounts.setBootstrapServers(List.of("localhost:9093")); + + return AsyncKafkaPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncKafkaPropsDomain.KafkaSecretFiller` +class. + +```java + +@Bean +@Primary +public AsyncKafkaPropsDomain.KafkaSecretFiller customKafkaFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + \ No newline at end of file diff --git a/docs/docs/scenarios/_category_.json b/docs/docs/scenarios/_category_.json new file mode 100644 index 00000000..ee603a10 --- /dev/null +++ b/docs/docs/scenarios/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Communication scenarios", + "position": 3, + "link": { + "type": "generated-index", + "description": "Review configuration examples for common scenarios." + } +} diff --git a/docs/package-lock.json b/docs/package-lock.json index 82daf899..f319895a 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,48 +8,49 @@ "name": "docs", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "^3.5.2", - "@docusaurus/preset-classic": "^3.5.2", - "@mdx-js/react": "^3.0.1", + "@docusaurus/core": "^3.6.3", + "@docusaurus/preset-classic": "^3.6.3", + "@docusaurus/theme-common": "^3.6.3", + "@mdx-js/react": "^3.1.0", "clsx": "^2.1.1", - "prism-react-renderer": "^2.3.1", + "prism-react-renderer": "^2.4.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.5.2", - "@docusaurus/types": "^3.5.2" + "@docusaurus/module-type-aliases": "^3.6.3", + "@docusaurus/types": "^3.6.3" }, "engines": { "node": ">=18.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -57,9 +58,9 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" @@ -86,6 +87,20 @@ "@algolia/cache-common": "4.24.0" } }, + "node_modules/@algolia/client-abtesting": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.15.0.tgz", + "integrity": "sha512-FaEM40iuiv1mAipYyiptP4EyxkJ8qHfowCpEeusdHUC4C7spATJYArD2rX3AxkVeREkDIgYEOuXcwKUbDCr7Nw==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/client-account": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", @@ -146,10 +161,23 @@ } }, "node_modules/@algolia/client-common": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.1.1.tgz", - "integrity": "sha512-jkQNQbGY+XQB3Eln7wqqdUZKBzG8lETcsaUk5gcMc6iIwyN/qW0v0fhpKPH+Kli+BImLxo0CWk12CvVvx2exWA==", - "peer": true, + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.15.0.tgz", + "integrity": "sha512-IofrVh213VLsDkPoSKMeM9Dshrv28jhDlBDLRcVJQvlL8pzue7PEB1EZ4UoJFYS3NSn7JOcJ/V+olRQzXlJj1w==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.15.0.tgz", + "integrity": "sha512-bDDEQGfFidDi0UQUCbxXOCdphbVAgbVmxvaV75cypBTQkJ+ABx/Npw7LkFGw1FsoVrttlrrQbwjvUB6mLVKs/w==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, "engines": { "node": ">= 14.0.0" } @@ -173,15 +201,29 @@ "@algolia/transporter": "4.24.0" } }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.15.0.tgz", + "integrity": "sha512-wu8GVluiZ5+il8WIRsGKu8VxMK9dAlr225h878GGtpTL6VBvwyJvAyLdZsfFIpY0iN++jiNb31q2C1PlPL+n/A==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/client-search": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.1.1.tgz", - "integrity": "sha512-SFpb3FI/VouGou/vpuS7qeCA5Y/KpV42P6CEA/1MZQtl/xJkl6PVjikb+Q9YadeHi2jtDV/aQ6PyiVDnX4PQcw==", - "peer": true, + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.15.0.tgz", + "integrity": "sha512-Z32gEMrRRpEta5UqVQA612sLdoqY3AovvUPClDfMxYrbdDAebmGDVPtSogUba1FZ4pP5dx20D3OV3reogLKsRA==", "dependencies": { - "@algolia/client-common": "5.1.1", - "@algolia/requester-browser-xhr": "5.1.1", - "@algolia/requester-node-http": "5.1.1" + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" }, "engines": { "node": ">= 14.0.0" @@ -192,6 +234,20 @@ "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" }, + "node_modules/@algolia/ingestion": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.15.0.tgz", + "integrity": "sha512-MkqkAxBQxtQ5if/EX2IPqFA7LothghVyvPoRNA/meS2AW2qkHwcxjuiBxv4H6mnAVEPfJlhu9rkdVz9LgCBgJg==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/logger-common": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", @@ -205,6 +261,20 @@ "@algolia/logger-common": "4.24.0" } }, + "node_modules/@algolia/monitoring": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.15.0.tgz", + "integrity": "sha512-QPrFnnGLMMdRa8t/4bs7XilPYnoUXDY8PMQJ1sf9ZFwhUysYYhQNX34/enoO0LBjpoOY6rLpha39YQEFbzgKyQ==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/recommend": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", @@ -259,12 +329,11 @@ } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.1.1.tgz", - "integrity": "sha512-NXmN1ujJCj5GlJQaMK6DbdiXdcf6nhRef/X40lu9TYi71q9xTo/5RPMI0K2iOp6g07S26BrXFOz6RSV3Ny4LLw==", - "peer": true, + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.15.0.tgz", + "integrity": "sha512-Po/GNib6QKruC3XE+WKP1HwVSfCDaZcXu48kD+gwmtDlqHWKc7Bq9lrS0sNZ456rfCKhXksOmMfUs4wRM/Y96w==", "dependencies": { - "@algolia/client-common": "5.1.1" + "@algolia/client-common": "5.15.0" }, "engines": { "node": ">= 14.0.0" @@ -275,13 +344,23 @@ "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==" }, + "node_modules/@algolia/requester-fetch": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.15.0.tgz", + "integrity": "sha512-rOZ+c0P7ajmccAvpeeNrUmEKoliYFL8aOR5qGW5pFq3oj3Iept7Y5mEtEsOBYsRt6qLnaXn4zUKf+N8nvJpcIw==", + "dependencies": { + "@algolia/client-common": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/requester-node-http": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.1.1.tgz", - "integrity": "sha512-xwrgnNTIzgxDEx6zuCKSKTPzQLA8fL/WZiVB6fRpIu5agLMjoAi0cWA5YSDbo+2FFxqVgLqKY/Jz6mKmWtY15Q==", - "peer": true, + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.15.0.tgz", + "integrity": "sha512-b1jTpbFf9LnQHEJP5ddDJKE2sAlhYd7EVSOWgzo/27n/SfCoHfqD0VWntnWYD83PnOKvfe8auZ2+xCb0TXotrQ==", "dependencies": { - "@algolia/client-common": "5.1.1" + "@algolia/client-common": "5.15.0" }, "engines": { "node": ">= 14.0.0" @@ -310,11 +389,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -322,28 +402,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", - "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.4", - "@babel/parser": "^7.24.4", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -367,49 +447,39 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", - "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dependencies": { - "@babel/types": "^7.24.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -426,18 +496,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", - "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -456,12 +524,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -480,9 +548,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", - "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -494,69 +562,38 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -566,32 +603,32 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -601,13 +638,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -616,185 +653,102 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", - "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", - "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dependencies": { - "color-convert": "^1.9.0" + "@babel/types": "^7.26.3" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", - "bin": { - "parser": "bin/babel-parser.js" + "node": ">=6.9.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz", - "integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -804,11 +758,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -818,13 +772,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -834,12 +788,12 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -859,42 +813,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -906,23 +824,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -932,11 +839,11 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -945,128 +852,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1076,11 +867,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1105,11 +896,11 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1119,14 +910,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", - "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1136,13 +926,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", - "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "dependencies": { - "@babel/helper-module-imports": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1152,11 +942,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1166,11 +956,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", - "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1180,12 +970,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1195,13 +985,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1211,17 +1000,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", - "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -1232,12 +1019,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1247,11 +1034,11 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", - "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1261,12 +1048,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1276,11 +1063,11 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1289,13 +1076,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1305,12 +1106,11 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1320,12 +1120,11 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1335,12 +1134,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1350,13 +1149,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1366,12 +1165,11 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1381,11 +1179,11 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1395,12 +1193,11 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1410,11 +1207,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1424,12 +1221,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1439,13 +1236,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1455,14 +1251,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1472,12 +1268,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1487,12 +1283,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1502,11 +1298,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1516,12 +1312,11 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1531,12 +1326,11 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1546,14 +1340,13 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", - "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.1" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1563,12 +1356,12 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1578,12 +1371,11 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1593,13 +1385,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", - "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1609,11 +1400,11 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", - "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1623,12 +1414,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1638,14 +1429,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", - "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1655,11 +1445,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1669,11 +1459,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.1.tgz", - "integrity": "sha512-SLV/giH/V4SmloZ6Dt40HjTGTAIkxn33TVIHxNGNvo8ezMhrxBkzisj4op1KZYPIOHFLqhv60OHvX+YRu4xbmQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", + "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1683,11 +1473,11 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", - "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1697,15 +1487,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1715,11 +1505,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.22.5" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1729,12 +1519,12 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz", - "integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1744,11 +1534,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1758,12 +1548,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1773,14 +1578,14 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", - "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", "dependencies": { - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -1800,11 +1605,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1814,12 +1619,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1829,11 +1634,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1843,11 +1648,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1857,11 +1662,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", - "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1871,14 +1676,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz", - "integrity": "sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz", + "integrity": "sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-typescript": "^7.24.1" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1888,11 +1694,11 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1902,12 +1708,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1917,12 +1723,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1932,12 +1738,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1947,90 +1753,78 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz", - "integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==", - "dependencies": { - "@babel/compat-data": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.1", - "@babel/plugin-syntax-import-attributes": "^7.24.1", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.1", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.24.1", - "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.4", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-classes": "^7.24.1", - "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.1", - "@babel/plugin-transform-dotall-regex": "^7.24.1", - "@babel/plugin-transform-duplicate-keys": "^7.24.1", - "@babel/plugin-transform-dynamic-import": "^7.24.1", - "@babel/plugin-transform-exponentiation-operator": "^7.24.1", - "@babel/plugin-transform-export-namespace-from": "^7.24.1", - "@babel/plugin-transform-for-of": "^7.24.1", - "@babel/plugin-transform-function-name": "^7.24.1", - "@babel/plugin-transform-json-strings": "^7.24.1", - "@babel/plugin-transform-literals": "^7.24.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-member-expression-literals": "^7.24.1", - "@babel/plugin-transform-modules-amd": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-modules-systemjs": "^7.24.1", - "@babel/plugin-transform-modules-umd": "^7.24.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.24.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.1", - "@babel/plugin-transform-object-super": "^7.24.1", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.1", - "@babel/plugin-transform-parameters": "^7.24.1", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.1", - "@babel/plugin-transform-property-literals": "^7.24.1", - "@babel/plugin-transform-regenerator": "^7.24.1", - "@babel/plugin-transform-reserved-words": "^7.24.1", - "@babel/plugin-transform-shorthand-properties": "^7.24.1", - "@babel/plugin-transform-spread": "^7.24.1", - "@babel/plugin-transform-sticky-regex": "^7.24.1", - "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.1", - "@babel/plugin-transform-unicode-escapes": "^7.24.1", - "@babel/plugin-transform-unicode-property-regex": "^7.24.1", - "@babel/plugin-transform-unicode-regex": "^7.24.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -2062,16 +1856,16 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.1.tgz", - "integrity": "sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-transform-react-display-name": "^7.24.1", - "@babel/plugin-transform-react-jsx": "^7.23.4", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2081,15 +1875,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", - "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", + "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-syntax-jsx": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-typescript": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2098,15 +1892,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2115,9 +1904,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.4.tgz", - "integrity": "sha512-VOQOexSilscN24VEY810G/PqtpFvx/z6UqDIjIWbDe2368HhDLkYN5TYwaEz/+eRCUkhJ2WaNLLmQAlxzfWj4w==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" @@ -2127,31 +1916,28 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", - "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2160,13 +1946,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2181,209 +1966,1373 @@ "node": ">=0.1.90" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz", + "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=10.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, - "node_modules/@docsearch/css": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", - "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==" + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@docsearch/react": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", - "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.1", - "algoliasearch": "^4.19.1" + "node_modules/@csstools/css-calc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.0.tgz", + "integrity": "sha512-X69PmFOrjTZfN5ijxtI8hZ9kRADFSLrmmQ6hgDJ272Il049WGKpDY64KhrFm/7rbWve0z81QepawzjkKlqkNGw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" }, "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.6.tgz", + "integrity": "sha512-S/IjXqTHdpI4EtzGoNCHfqraXF37x12ZZHA1Lk7zoT5pm2lMjFuqhX/89L7dqX4CcMacKK+6ZCs5TmEGb/+wKw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.0" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "react": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "react-dom": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "search-insights": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, - "node_modules/@docusaurus/core": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", - "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", + "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@babel/core": "^7.23.3", - "@babel/generator": "^7.23.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.22.9", - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@babel/runtime": "^7.22.6", - "@babel/runtime-corejs3": "^7.22.6", - "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "autoprefixer": "^10.4.14", - "babel-loader": "^9.1.3", - "babel-plugin-dynamic-import-node": "^2.3.3", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "clean-css": "^5.3.2", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.31.1", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "del": "^6.1.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "html-minifier-terser": "^7.2.0", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.5.3", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.7.6", - "p-map": "^4.0.0", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", - "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "rtl-detect": "^1.0.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.5", - "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.88.1", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.9.0", - "webpackbar": "^5.0.2" + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" + "engines": { + "node": ">=18" }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss-selector-parser": "^7.0.0" } }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", - "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.4.38", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=18.0" + "node": ">=4" } }, - "node_modules/@docusaurus/logger": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", - "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.6.tgz", + "integrity": "sha512-EcvXfC60cTIumzpsxWuvVjb7rsJEHPvqn3jeMEBUaE3JSc4FRuP7mEQ+1eicxWmIrs3FtzMH9gR3sgA5TH+ebQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", - "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.6.tgz", + "integrity": "sha512-jVKdJn4+JkASYGhyPO+Wa5WXSx1+oUgaXb3JsjJn/BlrtFh5zjocCY7pwWi0nuP24V1fY7glQsxEYcYNy0dMFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^1.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", - "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", - "dependencies": { - "@docusaurus/types": "3.5.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", + "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.5.tgz", + "integrity": "sha512-mi8R6dVfA2nDoKM3wcEi64I8vOYEgQVtVKCfmLHXupeLpACfGAided5ddMt5f+CnEodNu4DifuVwb0I6fQDGGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.6.tgz", + "integrity": "sha512-0ke7fmXfc8H+kysZz246yjirAH6JFhyX9GTlyRnM0exHO80XcA9zeJpy5pOp5zo/AZiC/q5Pf+Hw7Pd6/uAoYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.6.tgz", + "integrity": "sha512-Itrbx6SLUzsZ6Mz3VuOlxhbfuyLTogG5DwEF1V8dAi24iMuvQPIHd7Ti+pNDp7j6WixndJGZaoNR0f9VSzwuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.6.tgz", + "integrity": "sha512-927Pqy3a1uBP7U8sTfaNdZVB0mNXzIrJO/GZ8us9219q9n06gOqCdfZ0E6d1P66Fm0fYHvxfDbfcUuwAn5UwhQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", + "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.0.tgz", + "integrity": "sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", + "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", + "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz", + "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.5.tgz", + "integrity": "sha512-sdh5i5GToZOIAiwhdntRWv77QDtsxP2r2gXW/WbLSCoLr00KTq/yiF1qlQ5XX2+lmiFa8rATKMcbwl3oXDMNew==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz", + "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.6.tgz", + "integrity": "sha512-Hptoa0uX+XsNacFBCIQKTUBrFKDiplHan42X73EklG6XmQLG7/aIvxoNhvZ7PvOWMt67Pw3bIlUY2nD6p5vL8A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", + "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.1.tgz", + "integrity": "sha512-Ab/tF8/RXktQlFwVhiC70UNfpFQRhtE5fQQoP2pO+KCPGLsLdWFiOuHgSRtBOqEshCVAzR4H6o38nhvRZq8deA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.6.tgz", + "integrity": "sha512-yxP618Xb+ji1I624jILaYM62uEmZcmbdmFoZHoaThw896sq0vU39kqTTF+ZNic9XyPtPMvq0vyvbgmHaszq8xg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.0.tgz", + "integrity": "sha512-SLcc20Nujx/kqbSwDmj6oaXgpy3UjFhBy1sfcqPgDkHfOIfUtUVH7OXO+j7BU4v/At5s61N5ZX6shvgPwluhsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.5.tgz", + "integrity": "sha512-G6SJ6hZJkhxo6UZojVlLo14MohH4J5J7z8CRBrxxUYy9JuZiIqUo5TBYyDGcE0PLdzpg63a7mHSJz3VD+gMwqw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz", + "integrity": "sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.5.tgz", + "integrity": "sha512-/YQThYkt5MLvAmVu7zxjhceCYlKrYddK6LEmK5I4ojlS6BmO9u2yO4+xjXzu2+NPYmHSTtP4NFSamBCMmJ1NJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.0.tgz", + "integrity": "sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==" + }, + "node_modules/@docsearch/react": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.0.tgz", + "integrity": "sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.0", + "algoliasearch": "^5.12.0" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/client-analytics": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.15.0.tgz", + "integrity": "sha512-lho0gTFsQDIdCwyUKTtMuf9nCLwq9jOGlLGIeQGKDxXF7HbiAysFIu5QW/iQr1LzMgDyM9NH7K98KY+BiIFriQ==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/client-personalization": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.15.0.tgz", + "integrity": "sha512-LfaZqLUWxdYFq44QrasCDED5bSYOswpQjSiIL7Q5fYlefAAUO95PzBPKCfUhSwhb4rKxigHfDkd81AvEicIEoA==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/recommend": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.15.0.tgz", + "integrity": "sha512-5eupMwSqMLDObgSMF0XG958zR6GJP3f7jHDQ3/WlzCM9/YIJiWIUoJFGsko9GYsA5xbLDHE/PhWtq4chcCdaGQ==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docsearch/react/node_modules/algoliasearch": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.15.0.tgz", + "integrity": "sha512-Yf3Swz1s63hjvBVZ/9f2P1Uu48GjmjCN+Esxb6MAONMGtZB1fRX8/S1AhUTtsuTlcGovbYLxpHgc7wEzstDZBw==", + "dependencies": { + "@algolia/client-abtesting": "5.15.0", + "@algolia/client-analytics": "5.15.0", + "@algolia/client-common": "5.15.0", + "@algolia/client-insights": "5.15.0", + "@algolia/client-personalization": "5.15.0", + "@algolia/client-query-suggestions": "5.15.0", + "@algolia/client-search": "5.15.0", + "@algolia/ingestion": "1.15.0", + "@algolia/monitoring": "1.15.0", + "@algolia/recommend": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.6.3.tgz", + "integrity": "sha512-7dW9Hat9EHYCVicFXYA4hjxBY38+hPuCURL8oRF9fySRm7vzNWuEOghA1TXcykuXZp0HLG2td4RhDxCvGG7tNw==", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.6.3", + "@docusaurus/utils": "3.6.3", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.6.3.tgz", + "integrity": "sha512-47JLuc8D4wA+6VOvmMd5fUC9rFppBQpQOnxDYiVXffm/DeV/wmm3sbpNd5Y+O+G2+nevLTRnvCm/qyancv0Y3A==", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.6.3", + "@docusaurus/cssnano-preset": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.1", + "null-loader": "^4.0.1", + "postcss": "^8.4.26", + "postcss-loader": "^7.3.3", + "postcss-preset-env": "^10.1.0", + "react-dev-utils": "^12.0.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.6.3.tgz", + "integrity": "sha512-xL7FRY9Jr5DWqB6pEnqgKqcMPJOX5V0pgWXi5lCiih11sUBmcFKM7c3+GyxcVeeWFxyYSDP3grLTWqJoP4P9Vw==", + "dependencies": { + "@docusaurus/babel": "3.6.3", + "@docusaurus/bundler": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "del": "^6.1.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.1", + "react-helmet-async": "^1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "rtl-detect": "^1.0.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.6", + "shelljs": "^0.8.5", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^4.15.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.6.3.tgz", + "integrity": "sha512-qP7SXrwZ+23GFJdPN4aIHQrZW+oH/7tzwEuc/RNL0+BdZdmIjYQqUxdXsjE4lFxLNZjj0eUrSNYIS6xwfij+5Q==", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.4.38", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.6.3.tgz", + "integrity": "sha512-xSubJixcNyMV9wMV4q0s47CBz3Rlc5jbcCCuij8pfQP8qn/DIpt0ks8W6hQWzHAedg/J/EwxxUOUrnEoKzJo8g==", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.6.3.tgz", + "integrity": "sha512-3iJdiDz9540ppBseeI93tWTDtUGVkxzh59nMq4ignylxMuXBLK8dFqVeaEor23v1vx6TrGKZ2FuLaTB+U7C0QQ==", + "dependencies": { + "@docusaurus/logger": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^1.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.6.3.tgz", + "integrity": "sha512-MjaXX9PN/k5ugNvfRZdWyKWq4FsrhN4LEXaj0pEmMebJuBNlFeGyKQUa9DRhJHpadNaiMLrbo9m3U7Ig5YlsZg==", + "dependencies": { + "@docusaurus/types": "3.6.3", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", "@types/react-router-dom": "*", "react-helmet-async": "*", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" @@ -2394,18 +3343,18 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", - "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.6.3.tgz", + "integrity": "sha512-k0ogWwwJU3pFRFfvW1kRVHxzf2DutLGaaLjAnHVEU6ju+aRP0Z5ap/13DHyPOfHeE4WKpn/M0TqjdwZAcY3kAw==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", @@ -2427,19 +3376,19 @@ } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", - "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.6.3.tgz", + "integrity": "sha512-r2wS8y/fsaDcxkm20W5bbYJFPzdWdEaTWVYjNxlHlcmX086eqQR1Fomlg9BHTJ0dLXPzAlbC8EN4XqMr3QzNCQ==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2458,15 +3407,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", - "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.6.3.tgz", + "integrity": "sha512-eHrmTgjgLZsuqfsYr5X2xEwyIcck0wseSofWrjTwT9FLOWp+KDmMAuVK+wRo7sFImWXZk3oV/xX/g9aZrhD7OA==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2480,13 +3429,13 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", - "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.6.3.tgz", + "integrity": "sha512-zB9GXfIZNPRfzKnNjU6xGVrqn9bPXuGhpjgsuc/YtcTDjnjhasg38NdYd5LEqXex5G/zIorQgWB3n6x/Ut62vQ==", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2500,13 +3449,13 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", - "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.6.3.tgz", + "integrity": "sha512-rCDNy1QW8Dag7nZq67pcum0bpFLrwvxJhYuVprhFh8BMBDxV0bY+bAkGHbSf68P3Bk9C3hNOAXX1srGLIDvcTA==", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "tslib": "^2.6.0" }, "engines": { @@ -2518,13 +3467,13 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", - "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.6.3.tgz", + "integrity": "sha512-+OyDvhM6rqVkQOmLVkQWVJAizEEfkPzVWtIHXlWPOCFGK9X4/AWeBSrU0WG4iMg9Z4zD4YDRrU+lvI4s6DSC+w==", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2537,13 +3486,13 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", - "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.6.3.tgz", + "integrity": "sha512-1M6UPB13gWUtN2UHX083/beTn85PlRI9ABItTl/JL1FJ5dJTWWFXXsHf9WW/6hrVwthwTeV/AGbGKvLKV+IlCA==", "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "tslib": "^2.6.0" }, "engines": { @@ -2555,16 +3504,16 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", - "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.6.3.tgz", + "integrity": "sha512-94qOO4M9Fwv9KfVQJsgbe91k+fPJ4byf1L3Ez8TUa6TAFPo/BrLwQ80zclHkENlL1824TuxkcMKv33u6eydQCg==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2578,23 +3527,23 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", - "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/plugin-content-blog": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/plugin-content-pages": "3.5.2", - "@docusaurus/plugin-debug": "3.5.2", - "@docusaurus/plugin-google-analytics": "3.5.2", - "@docusaurus/plugin-google-gtag": "3.5.2", - "@docusaurus/plugin-google-tag-manager": "3.5.2", - "@docusaurus/plugin-sitemap": "3.5.2", - "@docusaurus/theme-classic": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-search-algolia": "3.5.2", - "@docusaurus/types": "3.5.2" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.6.3.tgz", + "integrity": "sha512-VHSYWROT3flvNNI1SrnMOtW1EsjeHNK9dhU6s9eY5hryZe79lUqnZJyze/ymDe2LXAqzyj6y5oYvyBoZZk6ErA==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/plugin-content-blog": "3.6.3", + "@docusaurus/plugin-content-docs": "3.6.3", + "@docusaurus/plugin-content-pages": "3.6.3", + "@docusaurus/plugin-debug": "3.6.3", + "@docusaurus/plugin-google-analytics": "3.6.3", + "@docusaurus/plugin-google-gtag": "3.6.3", + "@docusaurus/plugin-google-tag-manager": "3.6.3", + "@docusaurus/plugin-sitemap": "3.6.3", + "@docusaurus/theme-classic": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/theme-search-algolia": "3.6.3", + "@docusaurus/types": "3.6.3" }, "engines": { "node": ">=18.0" @@ -2605,26 +3554,27 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", - "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", - "dependencies": { - "@docusaurus/core": "3.5.2", - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/plugin-content-blog": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/plugin-content-pages": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-translations": "3.5.2", - "@docusaurus/types": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.6.3.tgz", + "integrity": "sha512-1RRLK1tSArI2c00qugWYO3jRocjOZwGF1mBzPPylDVRwWCS/rnWWR91ChdbbaxIupRJ+hX8ZBYrwr5bbU0oztQ==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/plugin-content-blog": "3.6.3", + "@docusaurus/plugin-content-docs": "3.6.3", + "@docusaurus/plugin-content-pages": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/theme-translations": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.44", + "infima": "0.2.0-alpha.45", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2644,14 +3594,14 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", - "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", - "dependencies": { - "@docusaurus/mdx-loader": "3.5.2", - "@docusaurus/module-type-aliases": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.6.3.tgz", + "integrity": "sha512-b8ZkhczXHDxWWyvz+YJy4t/PlPbEogTTbgnHoflYnH7rmRtyoodTsu8WVM12la5LmlMJBclBXFl29OH8kPE7gg==", + "dependencies": { + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2671,18 +3621,18 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", - "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.6.3.tgz", + "integrity": "sha512-rt+MGCCpYgPyWCGXtbxlwFbTSobu15jWBTPI2LHsHNa5B0zSmOISX6FWYAPt5X1rNDOqMGM0FATnh7TBHRohVA==", "dependencies": { "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.5.2", - "@docusaurus/logger": "3.5.2", - "@docusaurus/plugin-content-docs": "3.5.2", - "@docusaurus/theme-common": "3.5.2", - "@docusaurus/theme-translations": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-validation": "3.5.2", + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/plugin-content-docs": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/theme-translations": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "algoliasearch": "^4.18.0", "algoliasearch-helper": "^3.13.3", "clsx": "^2.0.0", @@ -2701,9 +3651,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", - "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.6.3.tgz", + "integrity": "sha512-Gb0regclToVlngSIIwUCtBMQBq48qVUaN1XQNKW4XwlsgUyk0vP01LULdqbem7czSwIeBAFXFoORJ0RPX7ht/w==", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -2713,9 +3663,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", - "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.6.3.tgz", + "integrity": "sha512-xD9oTGDrouWzefkhe9ogB2fDV96/82cRpNGx2HIvI5L87JHNhQVIWimQ/3JIiiX/TEd5S9s+VO6FFguwKNRVow==", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", @@ -2724,7 +3674,7 @@ "joi": "^17.9.2", "react-helmet-async": "^1.3.0", "utility-types": "^3.10.0", - "webpack": "^5.88.1", + "webpack": "^5.95.0", "webpack-merge": "^5.9.0" }, "peerDependencies": { @@ -2732,13 +3682,27 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@docusaurus/utils": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", - "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.6.3.tgz", + "integrity": "sha512-0R/FR3bKVl4yl8QwbL4TYFfR+OXBRpVUaTJdENapBGR3YMwfM6/JnhGilWQO8AOwPJGtGoDK7ib8+8UF9f3OZQ==", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils-common": "3.5.2", + "@docusaurus/logger": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-common": "3.6.3", "@svgr/webpack": "^8.1.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", @@ -2760,43 +3724,28 @@ }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-common": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", - "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.6.3.tgz", + "integrity": "sha512-v4nKDaANLgT3pMBewHYEMAl/ufY0LkXao1QkFWzI5huWFOmNQ2UFzv2BiKeHX5Ownis0/w6cAyoxPhVdDonlSQ==", "dependencies": { + "@docusaurus/types": "3.6.3", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", - "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.6.3.tgz", + "integrity": "sha512-bhEGGiN5BE38h21vjqD70Gxg++j+PfYVddDUE5UFvLDup68QOcpD33CLr+2knPorlxRbEaNfz6HQDUMQ3HuqKw==", "dependencies": { - "@docusaurus/logger": "3.5.2", - "@docusaurus/utils": "3.5.2", - "@docusaurus/utils-common": "3.5.2", + "@docusaurus/logger": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -2905,9 +3854,9 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@mdx-js/mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", - "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", @@ -2915,14 +3864,15 @@ "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", - "estree-util-to-js": "^2.0.0", + "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", - "hast-util-to-estree": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", - "periscopic": "^3.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", @@ -2939,9 +3889,9 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", - "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", "dependencies": { "@types/mdx": "^2.0.0" }, @@ -3024,9 +3974,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.25", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" }, "node_modules/@sideway/address": { "version": "4.1.5", @@ -3384,10 +4334,28 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -3531,9 +4499,9 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prismjs": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", - "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==" }, "node_modules/@types/prop-types": { "version": "15.7.12", @@ -3668,133 +4636,133 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -3840,9 +4808,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "bin": { "acorn": "bin/acorn" }, @@ -3850,14 +4818,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -3867,9 +4827,12 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -3959,9 +4922,9 @@ } }, "node_modules/algoliasearch-helper": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.4.tgz", - "integrity": "sha512-fvBCywguW9f+939S6awvRMstqMF1XXcd2qs1r1aGqL/PJ1go/DqN06tWmDVmhCDqBJanm++imletrQWf0G2S1g==", + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz", + "integrity": "sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw==", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -4030,6 +4993,31 @@ "node": ">=8" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -4099,9 +5087,9 @@ } }, "node_modules/astring": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", - "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "bin": { "astring": "bin/astring" } @@ -4151,9 +5139,9 @@ } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -4175,12 +5163,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", - "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.1", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -4196,23 +5184,23 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", - "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4356,9 +5344,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -4374,10 +5362,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4493,9 +5481,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", "funding": [ { "type": "opencollective", @@ -4743,17 +5731,6 @@ "node": ">=6" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4922,9 +5899,12 @@ } }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/content-disposition": { "version": "0.5.2", @@ -5045,11 +6025,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.24.2" }, "funding": { "type": "opencollective", @@ -5057,9 +6037,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz", - "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.39.0.tgz", + "integrity": "sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5097,9 +6077,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5134,6 +6114,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", @@ -5145,6 +6161,65 @@ "postcss": "^8.0.9" } }, + "node_modules/css-has-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.1.tgz", + "integrity": "sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -5222,6 +6297,27 @@ } } }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -5260,6 +6356,21 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssdb": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.2.tgz", + "integrity": "sha512-Z3kpWyvN68aKyeMxOUGmffQeHjvrzDxbre2B2ikr/WqQ4ZMkhHu2nOD6uwSeq3TpuOYU7ckvmJRAUIt6orkYUg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ] + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5765,9 +6876,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", - "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -5859,10 +6970,40 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==" }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -5981,6 +7122,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-util-to-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", @@ -5996,9 +7150,9 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", - "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.2.1.tgz", + "integrity": "sha512-Vt2UOjyPbNQQgT5eJh+K5aATti0OjCIAGc9SgMdOFYbohuifsWclR74l0iZTJwePMgWYdX1hlVS+dedH9XV8kw==", "dependencies": { "@types/estree": "^1.0.0" }, @@ -6220,14 +7374,6 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dependencies": { - "punycode": "^1.3.2" - } - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6270,6 +7416,28 @@ "node": ">=0.4.0" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -6982,14 +8150,14 @@ } }, "node_modules/hast-util-from-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", - "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", + "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", - "hastscript": "^8.0.0", + "hastscript": "^9.0.0", "property-information": "^6.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", @@ -7013,9 +8181,9 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", - "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -7063,10 +8231,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-estree/node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/hast-util-to-estree/node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", - "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", @@ -7089,19 +8270,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", - "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" - }, - "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", - "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", - "dependencies": { - "inline-style-parser": "0.2.3" - } - }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", @@ -7133,9 +8301,9 @@ } }, "node_modules/hastscript": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", - "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", + "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -7409,9 +8577,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -7555,9 +8723,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.44", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", - "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", "engines": { "node": ">=12" } @@ -7582,9 +8750,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" }, "node_modules/interpret": { "version": "1.4.0", @@ -7807,21 +8975,16 @@ } }, "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dependencies": { - "@types/estree": "*" - } - }, "node_modules/is-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", @@ -7936,9 +9099,9 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "bin": { "jiti": "bin/jiti.js" } @@ -7972,14 +9135,14 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -8075,9 +9238,9 @@ } }, "node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "engines": { "node": ">=14" }, @@ -8204,9 +9367,9 @@ } }, "node_modules/markdown-table": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", - "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8258,9 +9421,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", - "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -8281,9 +9444,9 @@ } }, "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8358,9 +9521,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8377,9 +9540,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8469,9 +9632,9 @@ } }, "node_modules/mdast-util-mdx-expression": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", - "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -8486,9 +9649,9 @@ } }, "node_modules/mdast-util-mdx-jsx": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz", - "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -8500,7 +9663,6 @@ "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", - "unist-util-remove-position": "^5.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, @@ -8560,15 +9722,16 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", - "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" @@ -8644,9 +9807,9 @@ } }, "node_modules/micromark": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", - "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", "funding": [ { "type": "GitHub Sponsors", @@ -8678,9 +9841,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", - "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", "funding": [ { "type": "GitHub Sponsors", @@ -8711,9 +9874,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8730,9 +9893,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8749,9 +9912,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8764,9 +9927,9 @@ ] }, "node_modules/micromark-extension-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.1.tgz", - "integrity": "sha512-VGV2uxUzhEZmaP7NSFo2vtq7M2nUD+WfmYQD+d8i/1nHbzE+rMy9uzTvUybBbNiVbrhOZibg3gbyoARGqgDWyg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -8782,9 +9945,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8801,9 +9964,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8820,9 +9983,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8850,9 +10013,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8869,9 +10032,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8918,9 +10081,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8937,9 +10100,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8971,9 +10134,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8990,9 +10153,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9009,9 +10172,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9041,9 +10204,9 @@ } }, "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9072,9 +10235,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9091,9 +10254,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9110,9 +10273,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9153,9 +10316,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9172,9 +10335,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9191,9 +10354,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9231,9 +10394,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9250,9 +10413,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9269,9 +10432,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9284,9 +10447,9 @@ ] }, "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", + "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", @@ -9295,6 +10458,7 @@ "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" @@ -9305,9 +10469,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9324,9 +10488,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9343,9 +10507,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9409,9 +10573,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9428,9 +10592,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9443,9 +10607,9 @@ ] }, "node_modules/micromark-factory-destination": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", - "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -9463,9 +10627,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9482,9 +10646,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9497,9 +10661,9 @@ ] }, "node_modules/micromark-factory-label": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", - "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -9518,9 +10682,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9537,9 +10701,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9552,9 +10716,9 @@ ] }, "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", - "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", + "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", "funding": [ { "type": "GitHub Sponsors", @@ -9568,6 +10732,7 @@ "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -9576,10 +10741,29 @@ "vfile-message": "^4.0.0" } }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9596,9 +10780,9 @@ } }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9645,9 +10829,9 @@ ] }, "node_modules/micromark-factory-title": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", - "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -9666,9 +10850,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9685,9 +10869,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9704,9 +10888,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9719,9 +10903,9 @@ ] }, "node_modules/micromark-factory-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", - "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -9740,9 +10924,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9759,9 +10943,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9778,9 +10962,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9827,9 +11011,9 @@ ] }, "node_modules/micromark-util-chunked": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", - "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -9845,9 +11029,9 @@ } }, "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9860,9 +11044,9 @@ ] }, "node_modules/micromark-util-classify-character": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", - "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9880,9 +11064,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9899,9 +11083,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9914,9 +11098,9 @@ ] }, "node_modules/micromark-util-combine-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", - "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -9933,9 +11117,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", - "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -9951,9 +11135,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9966,9 +11150,9 @@ ] }, "node_modules/micromark-util-decode-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", - "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -9987,9 +11171,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10006,9 +11190,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10021,9 +11205,9 @@ ] }, "node_modules/micromark-util-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", - "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -10061,9 +11245,9 @@ } }, "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10076,9 +11260,9 @@ ] }, "node_modules/micromark-util-html-tag-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", - "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { "type": "GitHub Sponsors", @@ -10091,9 +11275,9 @@ ] }, "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", - "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10109,9 +11293,9 @@ } }, "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10124,9 +11308,9 @@ ] }, "node_modules/micromark-util-resolve-all": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", - "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -10142,9 +11326,9 @@ } }, "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", - "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10162,9 +11346,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10181,9 +11365,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10196,9 +11380,9 @@ ] }, "node_modules/micromark-util-subtokenize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", - "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", "funding": [ { "type": "GitHub Sponsors", @@ -10217,9 +11401,9 @@ } }, "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10247,9 +11431,9 @@ ] }, "node_modules/micromark-util-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", - "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10262,9 +11446,9 @@ ] }, "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -10281,9 +11465,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10300,9 +11484,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10376,9 +11560,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -10444,9 +11628,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -10483,9 +11667,9 @@ } }, "node_modules/node-emoji": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -10525,31 +11709,95 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" } }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { - "boolbase": "^1.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/object-assign": { @@ -10820,22 +12068,22 @@ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "dependencies": { - "entities": "^4.4.0" + "entities": "^4.5.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "dependencies": { - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "parse5": "^7.0.0" }, "funding": { @@ -10909,165 +12157,450 @@ "node": ">=8" } }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { - "node": ">=8.6" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.6.tgz", + "integrity": "sha512-wLXvm8RmLs14Z2nVpB4CWlnvaWPRcOZFltJSlcbYwSJ1EDZKsKDhPKIMecCnuU054KSmlmubkqczmm6qBPCBhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "dependencies": { - "find-up": "^6.3.0" + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=14.16" + "node": "^14 || ^16 || >=18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "dependencies": { - "find-up": "^3.0.0" + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/postcss-custom-media": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz", + "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "locate-path": "^3.0.0" + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/postcss-custom-properties": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz", + "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/postcss-custom-selectors": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz", + "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "p-try": "^2.0.0" + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "p-limit": "^2.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "engines": { "node": ">=4" } }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "github", + "url": "https://github.com/sponsors/csstools" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.31" } }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11075,14 +12608,10 @@ "postcss": "^8.4.31" } }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11090,10 +12619,24 @@ "postcss": "^8.4.31" } }, - "node_modules/postcss-discard-comments": { + "node_modules/postcss-discard-overridden": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -11101,51 +12644,184 @@ "postcss": "^8.4.31" } }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "engines": { - "node": "^14 || ^16 || >=18.0" + "node_modules/postcss-double-position-gradients": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", + "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.1.0" } }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "node_modules/postcss-lab-function": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.6.tgz", + "integrity": "sha512-HPwvsoK7C949vBZ+eMyvH2cQeMr3UREoHvbtra76/UhDuiViZH6pir+z71UaJQohd7VDSVUdR6TkWYKExEc9aQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-selector-parser": "^6.0.16" + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, "node_modules/postcss-loader": { @@ -11169,6 +12845,30 @@ "webpack": "^5.0.0" } }, + "node_modules/postcss-logical": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.0.0.tgz", + "integrity": "sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-merge-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", @@ -11288,12 +12988,12 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", + "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -11303,12 +13003,24 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -11317,6 +13029,18 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -11331,6 +13055,86 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nesting": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", + "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", + "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", @@ -11409,65 +13213,264 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "peerDependencies": { + "postcss": "^8" } }, - "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "node_modules/postcss-preset-env": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.1.tgz", + "integrity": "sha512-wqqsnBFD6VIwcHHRbhjTOcOi4qRVlB26RwSr0ordPj7OubRRxdWebv/aLjKLRR8zkZrbxZyuus03nOIgC5elMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-value-parser": "^4.2.0" + "@csstools/postcss-cascade-layers": "^5.0.1", + "@csstools/postcss-color-function": "^4.0.6", + "@csstools/postcss-color-mix-function": "^3.0.6", + "@csstools/postcss-content-alt-text": "^2.0.4", + "@csstools/postcss-exponential-functions": "^2.0.5", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.6", + "@csstools/postcss-gradients-interpolation-method": "^5.0.6", + "@csstools/postcss-hwb-function": "^4.0.6", + "@csstools/postcss-ic-unit": "^4.0.0", + "@csstools/postcss-initial": "^2.0.0", + "@csstools/postcss-is-pseudo-class": "^5.0.1", + "@csstools/postcss-light-dark-function": "^2.0.7", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.3", + "@csstools/postcss-media-minmax": "^2.0.5", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.6", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-random-function": "^1.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.6", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.0", + "@csstools/postcss-stepped-value-functions": "^4.0.5", + "@csstools/postcss-text-decoration-shorthand": "^4.0.1", + "@csstools/postcss-trigonometric-functions": "^4.0.5", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.1", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.2.1", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.6", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.5", + "postcss-custom-properties": "^14.0.4", + "postcss-custom-selectors": "^8.0.4", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.0", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.6", + "postcss-logical": "^8.0.0", + "postcss-nesting": "^13.0.1", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=4" } }, "node_modules/postcss-reduce-idents": { @@ -11513,10 +13516,54 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11602,9 +13649,9 @@ } }, "node_modules/prism-react-renderer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", - "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz", + "integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==", "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" @@ -11682,11 +13729,6 @@ "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/pupa": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", @@ -11986,9 +14028,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", - "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz", + "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", "engines": { "node": ">=14" }, @@ -12111,6 +14153,66 @@ "node": ">= 0.10" } }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", + "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", @@ -12128,9 +14230,9 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dependencies": { "regenerate": "^1.4.2" }, @@ -12152,14 +14254,14 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -12192,25 +14294,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -12225,6 +14324,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -12296,9 +14409,9 @@ } }, "node_modules/remark-mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", - "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" @@ -12324,9 +14437,9 @@ } }, "node_modules/remark-rehype": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", - "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -12446,6 +14559,14 @@ "entities": "^2.0.0" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -12552,9 +14673,9 @@ "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" }, "node_modules/rtlcss": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.2.0.tgz", - "integrity": "sha512-AV+V3oOVvCrqyH5Q/6RuT1IDH1Xy5kJTkEWTWZPN5rdQ3HCFOd8SrbC7c6N5Y8bPpCfZSR6yYbUATXslvfvu5g==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -12646,9 +14767,9 @@ } }, "node_modules/search-insights": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.16.3.tgz", - "integrity": "sha512-hSHy/s4Zk2xibhj9XTCACB+1PqS+CaJxepGNBhKc/OsHRpqvHAUAm5+uZ6kJJbGXn0pb3XqekHjg6JAqPExzqg==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "peer": true }, "node_modules/section-matter": { @@ -12790,24 +14911,23 @@ } }, "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" }, "node_modules/serve-index": { "version": "1.9.1", @@ -13091,9 +15211,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -13177,9 +15297,9 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==" }, "node_modules/string_decoder": { "version": "1.3.0", @@ -13295,11 +15415,11 @@ } }, "node_modules/style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", "dependencies": { - "inline-style-parser": "0.1.1" + "inline-style-parser": "0.2.4" } }, "node_modules/stylehacks": { @@ -13531,14 +15651,6 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -13658,9 +15770,9 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "engines": { "node": ">=4" } @@ -13686,9 +15798,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "engines": { "node": ">=4" } @@ -13769,19 +15881,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -13838,9 +15937,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -13856,8 +15955,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -14090,12 +16189,11 @@ } }, "node_modules/vfile": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz", - "integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "dependencies": { "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, "funding": { @@ -14159,17 +16257,17 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -14204,9 +16302,9 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", - "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", @@ -14216,7 +16314,6 @@ "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", - "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", @@ -14365,16 +16462,16 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -14450,22 +16547,72 @@ } }, "node_modules/webpackbar": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", - "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.3", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.0.1" + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.21.3" }, "peerDependencies": { "webpack": "3 || 4 || 5" } }, + "node_modules/webpackbar/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpackbar/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -14644,9 +16791,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "engines": { "node": ">=12.20" }, diff --git a/docs/package.json b/docs/package.json index eed0a7f1..45195f20 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,17 +14,18 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "^3.5.2", - "@docusaurus/preset-classic": "^3.5.2", - "@mdx-js/react": "^3.0.1", + "@docusaurus/core": "^3.6.3", + "@docusaurus/preset-classic": "^3.6.3", + "@docusaurus/theme-common": "^3.6.3", + "@mdx-js/react": "^3.1.0", "clsx": "^2.1.1", - "prism-react-renderer": "^2.3.1", + "prism-react-renderer": "^2.4.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.5.2", - "@docusaurus/types": "^3.5.2" + "@docusaurus/module-type-aliases": "^3.6.3", + "@docusaurus/types": "^3.6.3" }, "browserslist": { "production": [ diff --git a/docs/src/components/ThemeImage/index.js b/docs/src/components/ThemeImage/index.js new file mode 100644 index 00000000..956d892a --- /dev/null +++ b/docs/src/components/ThemeImage/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { useColorMode } from '@docusaurus/theme-common'; + +const scenarios = { + '1': { + dark: require('@site/static/img/scenarios/1-scenario-dark.svg').default, + light: require('@site/static/img/scenarios/1-scenario.svg').default + }, + '2': { + dark: require('@site/static/img/scenarios/2-scenario-dark.svg').default, + light: require('@site/static/img/scenarios/2-scenario.svg').default + }, +// '3': { +// dark: require('@site/static/img/scenarios/3-scenario-dark.svg').default, +// light: require('@site/static/img/scenarios/3-scenario.svg').default +// }, +} + +console.log(scenarios) + +const ThemeImage = ({ scenario }) => { + const { colorMode } = useColorMode(); + + // Import the images using `require` + const Svg = scenarios[scenario][colorMode]; + + return ( + + ); +}; + +export default ThemeImage; diff --git a/docs/static/img/scenarios/1-scenario-dark.svg b/docs/static/img/scenarios/1-scenario-dark.svg new file mode 100644 index 00000000..178d099f --- /dev/null +++ b/docs/static/img/scenarios/1-scenario-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + APP 2APP 1BrokeremitlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...) \ No newline at end of file diff --git a/docs/static/img/scenarios/1-scenario.svg b/docs/static/img/scenarios/1-scenario.svg new file mode 100644 index 00000000..57a42d7a --- /dev/null +++ b/docs/static/img/scenarios/1-scenario.svg @@ -0,0 +1,10 @@ + + + + + + + + APP 2APP 1BrokeremitlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...) \ No newline at end of file diff --git a/docs/static/img/scenarios/2-scenario-dark.svg b/docs/static/img/scenarios/2-scenario-dark.svg new file mode 100644 index 00000000..65ffb6e0 --- /dev/null +++ b/docs/static/img/scenarios/2-scenario-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + APP 3APP 2Broker 2emitDirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Broker 1listenAPP 1emitDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Context AContext BHandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitlistenlistenlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitHandlerRegistry.listenDomainEvent(...)HandlerRegistry.listenDomainCloudEvent(...) \ No newline at end of file diff --git a/docs/static/img/scenarios/2-scenario.svg b/docs/static/img/scenarios/2-scenario.svg new file mode 100644 index 00000000..c0ecaf08 --- /dev/null +++ b/docs/static/img/scenarios/2-scenario.svg @@ -0,0 +1,11 @@ + + + + + + + + APP 3APP 2Broker 2emitDirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Broker 1listenAPP 1emitDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Context AContext BHandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitlistenlistenlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitHandlerRegistry.listenDomainEvent(...)HandlerRegistry.listenDomainCloudEvent(...) \ No newline at end of file diff --git a/main.gradle b/main.gradle index 64978624..c6da6a54 100644 --- a/main.gradle +++ b/main.gradle @@ -80,7 +80,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.3.4' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.0' } } @@ -176,5 +176,5 @@ tasks.register('generateMergedReport', JacocoReport) { } tasks.named('wrapper') { - gradleVersion = '8.10.2' + gradleVersion = '8.11' } \ No newline at end of file diff --git a/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle b/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle index 78766eea..7a8e2851 100644 --- a/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle +++ b/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle @@ -6,4 +6,5 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' } \ No newline at end of file diff --git a/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java index 00e060c3..a2087551 100644 --- a/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java +++ b/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java @@ -1,5 +1,6 @@ package sample; +import io.cloudevents.CloudEvent; import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; @@ -22,6 +23,8 @@ import sample.model.broker.AnimalEvent; import sample.model.broker.RemovedMemberEvent; +import java.net.URI; +import java.time.OffsetDateTime; import java.util.UUID; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 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 index 40653de7..1f7c1986 100644 --- 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 @@ -118,7 +118,7 @@ public DefaultCommandHandler defaultCommandHandler() { } @Bean - @ConditionalOnMissingBean(HandlerRegistry.class) + @ConditionalOnMissingBean public HandlerRegistry defaultHandlerRegistry() { return HandlerRegistry.register(); } 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 index c649942d..dd08bc0f 100644 --- 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 @@ -8,12 +8,12 @@ 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) + @ConditionalOnClass(AbstractReactiveHealthIndicator.class) public ReactiveCommonsHealthIndicator reactiveCommonsHealthIndicator(ConnectionManager manager) { return new ReactiveCommonsHealthIndicator(manager); } From d8b4c2f18dcea8adddf288508cd1155a8e57366d Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Thu, 5 Dec 2024 21:52:42 +0000 Subject: [PATCH 23/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92bda4d2..bf172a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.1.1-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.1-beta) (2024-12-05) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.0-beta...v5.1.1-beta) + +**Merged pull requests:** + +- docs\(scenarios\): Add scenarios documentation [\#125](https://github.com/reactive-commons/reactive-commons-java/pull/125) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.1.0-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.0-beta) (2024-10-11) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.1-beta...v5.1.0-beta) diff --git a/gradle.properties b/gradle.properties index 2427c03b..faee47a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.1.0-beta +version=5.1.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,async-commons-starter onlyUpdater=true \ No newline at end of file From 1819ce5a5d4d609c4123f05b5b27ccb5291b5a3b Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Sat, 14 Dec 2024 10:31:13 -0500 Subject: [PATCH 24/50] chore(health): ignore health indicator when dependency not exists (#126) * fix(health): ignore health indicator when dependency not exists --- .../async/starter/broker/BrokerProvider.java | 4 +- .../async/starter/config/health/RCHealth.java | 46 +++++++++++++++++++ .../config/health/RCHealthIndicator.java | 13 ++++++ .../health/ReactiveCommonsHealthConfig.java | 2 +- .../ReactiveCommonsHealthIndicator.java | 9 ++-- .../ReactiveCommonsHealthIndicatorTest.java | 8 ++-- .../starter/mybroker/MyBrokerProvider.java | 4 +- .../async/kafka/KafkaBrokerProvider.java | 4 +- .../health/KafkaReactiveHealthIndicator.java | 8 ++-- .../async/kafka/KafkaBrokerProviderTest.java | 8 ++-- .../KafkaReactiveHealthIndicatorTest.java | 9 ++-- .../async/rabbit/RabbitMQBrokerProvider.java | 4 +- .../health/RabbitReactiveHealthIndicator.java | 8 ++-- .../rabbit/RabbitMQBrokerProviderTest.java | 8 ++-- .../RabbitReactiveHealthIndicatorTest.java | 16 +++---- 15 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java 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 index 88d438d4..539f7330 100644 --- 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 @@ -3,8 +3,8 @@ import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.config.health.RCHealth; import org.reactivecommons.async.starter.props.GenericAsyncProps; -import org.springframework.boot.actuate.health.Health; import reactor.core.publisher.Mono; @SuppressWarnings("rawtypes") @@ -25,5 +25,5 @@ public interface BrokerProvider { void listenReplies(HandlerResolver resolver); - Mono healthCheck(); + Mono healthCheck(); } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java new file mode 100644 index 00000000..13972fc8 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java @@ -0,0 +1,46 @@ +package org.reactivecommons.async.starter.config.health; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Builder +@AllArgsConstructor +public class RCHealth { + private final Status status; + private final Map details; + + public enum Status { + UP, + DOWN + } + + public static class RCHealthBuilder { + public RCHealthBuilder() { + this.details = new HashMap<>(); + } + + public RCHealthBuilder up() { + this.status = Status.UP; + return this; + } + + public RCHealthBuilder down() { + this.status = Status.DOWN; + return this; + } + + public RCHealthBuilder withDetail(String key, Object value) { + this.details.put(key, value); + return this; + } + + public RCHealth build() { + return new RCHealth(this.status, this.details); + } + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java new file mode 100644 index 00000000..04cf1aec --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java @@ -0,0 +1,13 @@ +package org.reactivecommons.async.starter.config.health; + +import reactor.core.publisher.Mono; + +public abstract class RCHealthIndicator { + + public Mono health() { + return doHealthCheck(RCHealth.builder()) + .onErrorResume(e -> Mono.just(RCHealth.builder().down().withDetail("error", e.getMessage()).build())); + } + + public abstract Mono doHealthCheck(RCHealth.RCHealthBuilder builder); +} 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 index dd08bc0f..c649942d 100644 --- 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 @@ -8,12 +8,12 @@ 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) - @ConditionalOnClass(AbstractReactiveHealthIndicator.class) 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 index 624f651b..6b316cc3 100644 --- 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 @@ -2,11 +2,10 @@ import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.reactivecommons.async.starter.config.ConnectionManager; import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; 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; @@ -22,14 +21,14 @@ public class ReactiveCommonsHealthIndicator extends AbstractReactiveHealthIndica 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)) + .reduceWith(Health::up, (health, status) -> reduceHealth((Health.Builder) health, (RCHealth) status)) .map(b -> ((Health.Builder) b).build()); } - private Health.Builder reduceHealth(Health.Builder builder, Health status) { + private Health.Builder reduceHealth(Health.Builder builder, RCHealth status) { String domain = status.getDetails().get(DOMAIN).toString(); - if (status.getStatus().equals(Status.DOWN)) { + if (status.getStatus().equals(RCHealth.Status.DOWN)) { log.error("Broker of domain {} is down", domain); return builder.down().withDetail(domain, status.getDetails()); } 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 index d2c9b014..b616dd74 100644 --- 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 @@ -37,11 +37,11 @@ void setUp() { @Test void shouldBeUp() { // Arrange - when(brokerProvider.healthCheck()).thenReturn(Mono.just(Health.up() + when(brokerProvider.healthCheck()).thenReturn(Mono.just(RCHealth.builder().up() .withDetail(DOMAIN, DEFAULT_DOMAIN) .withDetail(VERSION, "123") .build())); - when(brokerProvider2.healthCheck()).thenReturn(Mono.just(Health.up() + when(brokerProvider2.healthCheck()).thenReturn(Mono.just(RCHealth.builder().up() .withDetail(DOMAIN, OTHER) .withDetail(VERSION, "1234") .build())); @@ -56,11 +56,11 @@ void shouldBeUp() { @Test void shouldBeDown() { // Arrange - when(brokerProvider.healthCheck()).thenReturn(Mono.just(Health.up() + when(brokerProvider.healthCheck()).thenReturn(Mono.just(RCHealth.builder().down() .withDetail(DOMAIN, DEFAULT_DOMAIN) .withDetail(VERSION, "123") .build())); - when(brokerProvider2.healthCheck()).thenReturn(Mono.just(Health.down() + when(brokerProvider2.healthCheck()).thenReturn(Mono.just(RCHealth.builder().up() .withDetail(DOMAIN, OTHER) .withDetail(VERSION, "1234") .build())); 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 index 95f5e976..f9c0365a 100644 --- 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 @@ -6,8 +6,8 @@ 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.config.health.RCHealth; import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; -import org.springframework.boot.actuate.health.Health; import reactor.core.publisher.Mono; @AllArgsConstructor @@ -57,7 +57,7 @@ public void listenReplies(HandlerResolver resolver) { } @Override - public Mono healthCheck() { + public Mono healthCheck() { return null; } } \ No newline at end of file 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 index b2ac1965..ea3b6732 100644 --- 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 @@ -19,7 +19,7 @@ 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.reactivecommons.async.starter.config.health.RCHealth; import org.springframework.boot.ssl.SslBundles; import reactor.core.publisher.Mono; @@ -100,7 +100,7 @@ public void listenReplies(HandlerResolver resolver) { } @Override - public Mono healthCheck() { + public Mono healthCheck() { return healthIndicator.health(); } } 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 index 2e2e8c30..ed49de49 100644 --- 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 @@ -3,8 +3,8 @@ 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 org.reactivecommons.async.starter.config.health.RCHealth; +import org.reactivecommons.async.starter.config.health.RCHealthIndicator; import reactor.core.publisher.Mono; import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; @@ -12,12 +12,12 @@ @Log4j2 @AllArgsConstructor -public class KafkaReactiveHealthIndicator extends AbstractReactiveHealthIndicator { +public class KafkaReactiveHealthIndicator extends RCHealthIndicator { private final String domain; private final AdminClient adminClient; @Override - protected Mono doHealthCheck(Health.Builder builder) { + public Mono doHealthCheck(RCHealth.RCHealthBuilder builder) { builder.withDetail(DOMAIN, domain); return checkKafkaHealth() .map(clusterId -> builder.up().withDetail(VERSION, clusterId).build()) 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 index c0f862a4..0db6bd4c 100644 --- 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 @@ -20,7 +20,7 @@ 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.reactivecommons.async.starter.config.health.RCHealth; import org.springframework.boot.ssl.DefaultSslBundleRegistry; import org.springframework.boot.ssl.SslBundles; import reactor.core.publisher.Flux; @@ -136,12 +136,12 @@ void shouldListenNotificationEvents() { @Test void shouldProxyHealthCheck() { - when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> Health.up().build())); + when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> RCHealth.builder().up().build())); // Act - Mono flow = brokerProvider.healthCheck(); + Mono flow = brokerProvider.healthCheck(); // Assert StepVerifier.create(flow) - .expectNextMatches(health -> health.getStatus().getCode().equals("UP")) + .expectNextMatches(health -> health.getStatus().equals(RCHealth.Status.UP)) .verifyComplete(); } } 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 index 0284ec4c..132bd660 100644 --- 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 @@ -9,6 +9,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.starter.config.health.RCHealth; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.boot.actuate.health.Status; @@ -39,13 +40,13 @@ void shouldBeUp() { when(adminClient.describeCluster()).thenReturn(describeClusterResult); when(describeClusterResult.clusterId()).thenReturn(KafkaFuture.completedFuture("cluster123")); // Act - Mono result = indicator.doHealthCheck(new Builder()); + Mono result = indicator.doHealthCheck(RCHealth.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()); + assertEquals(RCHealth.Status.UP, health.getStatus()); }) .verifyComplete(); } @@ -58,12 +59,12 @@ void shouldBeDown() { future.completeExceptionally(new RuntimeException("simulate error")); when(describeClusterResult.clusterId()).thenReturn(future); // Act - Mono result = indicator.doHealthCheck(new Builder()); + Mono result = indicator.doHealthCheck(RCHealth.builder()); // Assert StepVerifier.create(result) .expectNextMatches(health -> { assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); - assertEquals(Status.DOWN, health.getStatus()); + assertEquals(RCHealth.Status.DOWN, health.getStatus()); return true; }) .verifyComplete(); 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 index 5110e069..b116ced6 100644 --- 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 @@ -22,7 +22,7 @@ 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 org.reactivecommons.async.starter.config.health.RCHealth; import reactor.core.publisher.Mono; import static reactor.rabbitmq.ExchangeSpecification.exchange; @@ -154,7 +154,7 @@ public void listenReplies(HandlerResolver resolver) { } @Override - public Mono healthCheck() { + public Mono healthCheck() { return healthIndicator.health(); } } 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 index d8d0178e..a80d5f1a 100644 --- 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 @@ -4,8 +4,8 @@ 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 org.reactivecommons.async.starter.config.health.RCHealth; +import org.reactivecommons.async.starter.config.health.RCHealthIndicator; import reactor.core.publisher.Mono; import java.net.SocketException; @@ -14,7 +14,7 @@ import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; @Log4j2 -public class RabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { +public class RabbitReactiveHealthIndicator extends RCHealthIndicator { private final String domain; private final ConnectionFactory connectionFactory; @@ -25,7 +25,7 @@ public RabbitReactiveHealthIndicator(String domain, ConnectionFactory connection } @Override - protected Mono doHealthCheck(Health.Builder builder) { + public Mono doHealthCheck(RCHealth.RCHealthBuilder builder) { builder.withDetail(DOMAIN, domain); return Mono.fromCallable(() -> getRawVersion(connectionFactory)) .map(status -> builder.up().withDetail(VERSION, status).build()); 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 index 5fddb65e..cb25344f 100644 --- 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 @@ -23,7 +23,7 @@ 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 org.reactivecommons.async.starter.config.health.RCHealth; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.BindingSpecification; @@ -181,12 +181,12 @@ void shouldListenQueries() { @Test void shouldProxyHealthCheck() { - when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> Health.up().build())); + when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> RCHealth.builder().up().build())); // Act - Mono flow = brokerProvider.healthCheck(); + Mono flow = brokerProvider.healthCheck(); // Assert StepVerifier.create(flow) - .expectNextMatches(health -> health.getStatus().getCode().equals("UP")) + .expectNextMatches(health -> health.getStatus().equals(RCHealth.Status.UP)) .verifyComplete(); } } diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java index e2f2cc7e..3ebf2e22 100644 --- a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java @@ -7,9 +7,7 @@ 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 org.reactivecommons.async.starter.config.health.RCHealth; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -47,13 +45,13 @@ void shouldBeUp() throws IOException, TimeoutException { when(factory.newConnection()).thenReturn(connection); when(connection.getServerProperties()).thenReturn(properties); // Act - Mono result = indicator.doHealthCheck(new Builder()); + Mono result = indicator.doHealthCheck(RCHealth.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()); + assertEquals(RCHealth.Status.UP, health.getStatus()); }) .verifyComplete(); } @@ -67,13 +65,13 @@ void shouldBeUpAndIgnoreCloseError() throws IOException, TimeoutException { when(connection.getServerProperties()).thenReturn(properties); doThrow(new IOException("Error closing connection")).when(connection).close(); // Act - Mono result = indicator.doHealthCheck(new Builder()); + Mono result = indicator.doHealthCheck(RCHealth.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()); + assertEquals(RCHealth.Status.UP, health.getStatus()); }) .verifyComplete(); } @@ -83,7 +81,7 @@ void shouldBeDown() throws IOException, TimeoutException { // Arrange when(factory.newConnection()).thenThrow(new TimeoutException("Connection timeout")); // Act - Mono result = indicator.doHealthCheck(new Builder()); + Mono result = indicator.doHealthCheck(RCHealth.builder()); // Assert StepVerifier.create(result) .expectError(TimeoutException.class) @@ -95,7 +93,7 @@ void shouldBeDownWhenSocketException() throws IOException, TimeoutException { // Arrange when(factory.newConnection()).thenThrow(new SocketException("Connection timeout")); // Act - Mono result = indicator.doHealthCheck(new Builder()); + Mono result = indicator.doHealthCheck(RCHealth.builder()); // Assert StepVerifier.create(result) .expectError(RuntimeException.class) From 878c0a78e78a77a0f3005ad7f0fed9f8f18815cd Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Sat, 14 Dec 2024 15:38:17 +0000 Subject: [PATCH 25/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf172a9c..2e3bd437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.1.2-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.2-beta) (2024-12-14) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.1-beta...v5.1.2-beta) + +**Merged pull requests:** + +- chore\(health\): ignore health indicator when dependency not exists [\#126](https://github.com/reactive-commons/reactive-commons-java/pull/126) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.1.1-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.1-beta) (2024-12-05) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.0-beta...v5.1.1-beta) diff --git a/gradle.properties b/gradle.properties index faee47a7..80e892f7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.1.1-beta +version=5.1.2-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,async-commons-starter onlyUpdater=true \ No newline at end of file From 775eac2d7332c56362fe99ffdd6170d5627dbb1b Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 20 Dec 2024 13:30:30 +0000 Subject: [PATCH 26/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 4 ++++ gradle.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e3bd437..0a935417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [v5.1.2](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.2) (2024-12-14) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.2-beta...v5.1.2) + ## [v5.1.2-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.2-beta) (2024-12-14) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.1-beta...v5.1.2-beta) diff --git a/gradle.properties b/gradle.properties index 80e892f7..95478f6e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.1.2-beta +version=5.1.2 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 From 583c2359d41465eacf30d029b50acfab380b6529 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 27 Dec 2024 09:34:44 -0500 Subject: [PATCH 27/50] fix(deps): Update deps and docs for secret filler and log when rabbit connection error (#127) * Update docs for secret filler and log when rabbit connection error * Update deps --- build.gradle | 6 +++--- .../reactive-commons/9-configuration-properties.md | 5 ++--- main.gradle | 2 +- .../async/rabbit/RabbitMQSetupUtils.java | 12 ++++++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 1bf774f3..732ba3fb 100644 --- a/build.gradle +++ b/build.gradle @@ -12,10 +12,10 @@ buildscript { plugins { id 'jacoco' - id 'org.sonarqube' version '6.0.0.5145' - id 'org.springframework.boot' version '3.4.0' apply false + id 'org.sonarqube' version '6.0.1.5171' + id 'org.springframework.boot' version '3.4.1' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.20.2' + id 'co.com.bancolombia.cleanArchitecture' version '3.20.7' } repositories { diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index 5d55f9e1..d863d124 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -105,13 +105,12 @@ public class MyDomainConfig { } ``` -Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.SecretFiller` class. +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.RabbitSecretFiller` class. ```java @Bean -@Primary -public AsyncPropsDomain.SecretFiller customFiller() { +public AsyncPropsDomain.RabbitSecretFiller customFiller() { return (domain, asyncProps) -> { // customize asyncProps here by domain }; diff --git a/main.gradle b/main.gradle index c6da6a54..86094ef5 100644 --- a/main.gradle +++ b/main.gradle @@ -80,7 +80,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.0' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.1' } } 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 index 0d102085..a88dde91 100644 --- 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 @@ -90,8 +90,10 @@ public static DiscardNotifier createDiscardNotifier(ReactiveMessageSender sender return new DLQDiscardNotifier(appDomainEventBus, converter); } - private static SenderOptions reactiveCommonsSenderOptions(String appName, ConnectionFactoryProvider provider, RabbitProperties rabbitProperties) { - final Mono senderConnection = createConnectionMono(provider.getConnectionFactory(), appName, SENDER_TYPE); + 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(); @@ -109,10 +111,12 @@ private static SenderOptions reactiveCommonsSenderOptions(String appName, Connec .transform(Utils::cache)); } - private static Mono createConnectionMono(ConnectionFactory factory, String connectionPrefix, String connectionType) { + 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) + log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host" + + factory.getHost() + ". Starting retry process...", err) ) .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) .maxBackoff(Duration.ofMillis(3000))) From 62107bbd389b2730a890642546880798c32f5ba6 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 27 Dec 2024 14:41:02 +0000 Subject: [PATCH 28/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 12 ++++++++++++ gradle.properties | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a935417..4f3e01c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [v5.1.3](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.3) (2024-12-27) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.2...v5.1.3) + +**Closed issues:** + +- Compile error message when using reactive-commons-java and Spring Boot [\#93](https://github.com/reactive-commons/reactive-commons-java/issues/93) + +**Merged pull requests:** + +- fix\(docs\): Update docs for secret filler and log when rabbit connection error [\#127](https://github.com/reactive-commons/reactive-commons-java/pull/127) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.1.2](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.2) (2024-12-14) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.2-beta...v5.1.2) diff --git a/gradle.properties b/gradle.properties index 95478f6e..faf88fa9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.1.2 +version=5.1.3 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 From d67f86dc2f379d95f409b4ef464590da485cbf26 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:57:37 -0500 Subject: [PATCH 29/50] feat(tls): ssl default options (#128) * feat(tls): use default sslcontext when ssl is enabled, and allow ssl mapping from properties in rabbitmq --- .../async/rabbit/RabbitMQSetupUtils.java | 131 +++++++++++++++++- 1 file changed, 126 insertions(+), 5 deletions(-) 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 index a88dde91..c97c51f5 100644 --- 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 @@ -16,6 +16,7 @@ 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.config.spring.RabbitPropertiesBase; import org.springframework.boot.context.properties.PropertyMapper; import reactor.core.publisher.Mono; import reactor.rabbitmq.ChannelPool; @@ -29,7 +30,24 @@ import reactor.rabbitmq.Utils; import reactor.util.retry.Retry; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.time.Duration; +import java.util.Arrays; import java.util.logging.Level; @Log @@ -37,6 +55,25 @@ public class RabbitMQSetupUtils { private static final String LISTENER_TYPE = "listener"; private static final String SENDER_TYPE = "sender"; + private static final String DEFAULT_PROTOCOL; + public static final int START_INTERVAL = 300; + public static final int MAX_BACKOFF_INTERVAL = 3000; + + static { + String protocol = "TLSv1.1"; + try { + String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols(); + for (String prot : protocols) { + if ("TLSv1.2".equals(prot)) { + protocol = "TLSv1.2"; + break; + } + } + } catch (NoSuchAlgorithmException e) { + // nothing + } + DEFAULT_PROTOCOL = protocol; + } @SneakyThrows public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperties properties) { @@ -48,9 +85,7 @@ public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperti 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(); - } + setUpSSL(factory, properties); return () -> factory; } @@ -118,9 +153,95 @@ private static Mono createConnectionMono(ConnectionFactory factory, log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host" + factory.getHost() + ". Starting retry process...", err) ) - .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) - .maxBackoff(Duration.ofMillis(3000))) + .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(START_INTERVAL)) + .maxBackoff(Duration.ofMillis(MAX_BACKOFF_INTERVAL))) .cache(); } + // SSL based on RabbitConnectionFactoryBean + // https://github.com/spring-projects/spring-amqp/blob/main/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java + + private static void setUpSSL(ConnectionFactory factory, RabbitProperties properties) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException, + CertificateException, IOException { + var ssl = properties.getSsl(); + if (ssl != null && ssl.isEnabled()) { + var keyManagers = configureKeyManagers(ssl); + var trustManagers = configureTrustManagers(ssl); + var secureRandom = SecureRandom.getInstanceStrong(); + + if (log.isLoggable(Level.FINE)) { + log.fine("Initializing SSLContext with KM: " + Arrays.toString(keyManagers) + + ", TM: " + Arrays.toString(trustManagers) + ", random: " + secureRandom); + } + var context = createSSLContext(ssl); + context.init(keyManagers, trustManagers, secureRandom); + factory.useSslProtocol(context); + + logDetails(trustManagers); + + if (ssl.getVerifyHostname()) { + factory.enableHostnameVerification(); + } + } + } + + private static KeyManager[] configureKeyManagers(RabbitPropertiesBase.Ssl ssl) throws KeyStoreException, + IOException, + NoSuchAlgorithmException, + CertificateException, UnrecoverableKeyException { + KeyManager[] keyManagers = null; + if (ssl.getKeyStore() != null) { + var ks = KeyStore.getInstance(ssl.getKeyStoreType()); + char[] keyPassphrase = null; + if (ssl.getKeyStorePassword() != null) { + keyPassphrase = ssl.getKeyStorePassword().toCharArray(); + } + try (var inputStream = new FileInputStream(ssl.getKeyStore())) { + ks.load(inputStream, keyPassphrase); + } + var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, keyPassphrase); + keyManagers = kmf.getKeyManagers(); + } + return keyManagers; + } + + private static TrustManager[] configureTrustManagers(RabbitPropertiesBase.Ssl ssl) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore tks = null; + if (ssl.getTrustStore() != null) { + tks = KeyStore.getInstance(ssl.getTrustStoreType()); + char[] trustPassphrase = null; + if (ssl.getTrustStorePassword() != null) { + trustPassphrase = ssl.getTrustStorePassword().toCharArray(); + } + try (InputStream inputStream = new FileInputStream(ssl.getTrustStore())) { + tks.load(inputStream, trustPassphrase); + } + } + + var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(tks); + return tmf.getTrustManagers(); + } + + private static SSLContext createSSLContext(RabbitPropertiesBase.Ssl ssl) throws NoSuchAlgorithmException { + return SSLContext.getInstance(ssl.getAlgorithm() != null ? ssl.getAlgorithm() : DEFAULT_PROTOCOL); + } + + private static void logDetails(TrustManager[] managers) { + var found = false; + for (var trustManager : managers) { + if (trustManager instanceof X509TrustManager) { + found = true; + var x509TrustManager = (X509TrustManager) trustManager; + log.info("Loaded " + x509TrustManager.getAcceptedIssuers().length + " accepted issuers for rabbitmq"); + } + } + if (!found) { + log.warning("No X509TrustManager found in the truststore."); + } + } + } From 5c8738c00b11a2c86a383fe21b8d4f38c61f5f42 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 27 Dec 2024 21:03:36 +0000 Subject: [PATCH 30/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3e01c0..d8283887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.2.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.0) (2024-12-27) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.3...v5.2.0) + +**Merged pull requests:** + +- feat\(tls\): ssl default options [\#128](https://github.com/reactive-commons/reactive-commons-java/pull/128) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.1.3](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.3) (2024-12-27) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.2...v5.1.3) diff --git a/gradle.properties b/gradle.properties index faf88fa9..1abb9920 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.1.3 +version=5.2.0 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 From 0cb134ee733d938a88265c9a145443733dc293d6 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Wed, 8 Jan 2025 18:20:23 -0500 Subject: [PATCH 31/50] fix(circular-dependency): Extract listener config and remove handler resolver from replies listener (#129) * fix(circular-dependency): Extract listener config and remove handler resolver from replies listener * fix(log): add error log details * fix(test): Fix unit tests and update gradle --- .../async/commons/HandlerResolverBuilder.java | 31 ++++---- .../async/commons/utils/LoggerSubscriber.java | 4 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- main.gradle | 2 +- .../async/starter/broker/BrokerProvider.java | 4 +- .../starter/config/ReactiveCommonsConfig.java | 52 +------------- .../ReactiveCommonsListenersConfig.java | 70 +++++++++++++++++++ .../listeners/CommandsListenerConfig.java | 5 +- .../listeners/EventsListenerConfig.java | 3 +- .../NotificationEventsListenerConfig.java | 3 +- .../listeners/QueriesListenerConfig.java | 5 +- .../senders/DirectAsyncGatewayConfig.java | 5 +- .../starter/mybroker/MyBrokerProvider.java | 4 +- .../senders/DirectAsyncGatewayConfigTest.java | 11 +-- .../async/kafka/KafkaBrokerProvider.java | 4 +- .../async/kafka/KafkaBrokerProviderTest.java | 2 +- .../starter/impl/rabbit/KafkaConfigTest.java | 5 +- .../async/rabbit/RabbitMQBrokerProvider.java | 6 +- .../async/rabbit/RabbitMQSetupUtils.java | 4 +- .../rabbit/RabbitMQBrokerProviderTest.java | 2 +- .../impl/rabbit/RabbitMQConfigTest.java | 5 +- 22 files changed, 133 insertions(+), 98 deletions(-) create mode 100644 starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java index fb903322..62ec7479 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.logging.Level; import java.util.stream.Stream; import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @@ -45,35 +44,44 @@ public static HandlerResolver buildResolver(String domain, .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); - final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); + final ConcurrentMap> eventsToBind = getEventsToBind(domain, + registries); - final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); + final ConcurrentMap> eventHandlers = + getEventHandlersWithDynamics(domain, registries); - return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, commandHandlers) { + return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, + commandHandlers) { @Override @SuppressWarnings("unchecked") public RegisteredCommandHandler getCommandHandler(String path) { final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); + return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, + Object.class); } }; } final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); - final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); + final ConcurrentMap> eventHandlers = + getEventHandlersWithDynamics(domain, registries); - return new HandlerResolver(new ConcurrentHashMap<>(), eventHandlers, eventsToBind, new ConcurrentHashMap<>(), new ConcurrentHashMap<>()) { + return new HandlerResolver(new ConcurrentHashMap<>(), eventHandlers, eventsToBind, new ConcurrentHashMap<>(), + new ConcurrentHashMap<>()) { @Override @SuppressWarnings("unchecked") public RegisteredCommandHandler getCommandHandler(String path) { final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); + return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, + Object.class); } }; } - private static ConcurrentMap> getEventHandlersWithDynamics(String domain, Map registries) { + private static ConcurrentMap> getEventHandlersWithDynamics(String domain, + Map registries) { // event handlers and dynamic handlers return registries .values().stream() @@ -81,7 +89,6 @@ public RegisteredCommandHandler getCommandHandler(String path) { if (r.getDomainEventListeners().containsKey(domain)) { return Stream.concat(r.getDomainEventListeners().get(domain).stream(), getDynamics(domain, r)); } - log.log(Level.WARNING, "Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); return Stream.empty(); }) .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), @@ -95,14 +102,14 @@ public RegisteredCommandHandler getCommandHandler(String path) { return Stream.of(); } - private static ConcurrentMap> getEventsToBind(String domain, Map registries) { + private static ConcurrentMap> getEventsToBind(String domain, Map registries) { return registries .values().stream() .flatMap(r -> { if (r.getDomainEventListeners().containsKey(domain)) { return r.getDomainEventListeners().get(domain).stream(); } - log.log(Level.WARNING, "Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); return Stream.empty(); }) .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java index f7511312..0d2aff9c 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java @@ -14,7 +14,7 @@ public class LoggerSubscriber extends BaseSubscriber { private final String flowName; private static final String ON_COMPLETE_MSG = "%s: ##On Complete Hook!!"; - private static final String ON_ERROR_MSG = "%s: ##On Error Hook!!"; + private static final String ON_ERROR_MSG = "%s: ##On Error Hook!! %s"; private static final String ON_CANCEL_MSG = "%s: ##On Cancel Hook!!"; private static final String ON_FINALLY_MSG = "%s: ##On Finally Hook! Signal type: %s"; @@ -29,7 +29,7 @@ protected void hookOnComplete() { @Override protected void hookOnError(Throwable throwable) { - log.log(Level.SEVERE, format(ON_ERROR_MSG), throwable); + log.log(Level.SEVERE, format(ON_ERROR_MSG, throwable.getMessage()), throwable); } @Override diff --git a/build.gradle b/build.gradle index 732ba3fb..8ebc5ddf 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { id 'org.sonarqube' version '6.0.1.5171' id 'org.springframework.boot' version '3.4.1' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.20.7' + id 'co.com.bancolombia.cleanArchitecture' version '3.20.8' } repositories { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72b..e2847c82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/main.gradle b/main.gradle index 86094ef5..5796af1d 100644 --- a/main.gradle +++ b/main.gradle @@ -176,5 +176,5 @@ tasks.register('generateMergedReport', JacocoReport) { } tasks.named('wrapper') { - gradleVersion = '8.11' + gradleVersion = '8.11.1' } \ No newline at end of file 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 index 539f7330..771b9e88 100644 --- 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 @@ -13,7 +13,7 @@ public interface BrokerProvider { DomainEventBus getDomainBus(); - DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver); + DirectAsyncGateway getDirectAsyncGateway(); void listenDomainEvents(HandlerResolver resolver); @@ -23,7 +23,7 @@ public interface BrokerProvider { void listenQueries(HandlerResolver resolver); - void listenReplies(HandlerResolver resolver); + void listenReplies(); Mono healthCheck(); } 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 index 1f7c1986..1b612fdc 100644 --- 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 @@ -1,19 +1,13 @@ package org.reactivecommons.async.starter.config; +import com.fasterxml.jackson.databind.ObjectMapper; 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; @@ -27,7 +21,6 @@ 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; @@ -66,24 +59,6 @@ public ConnectionManager buildConnectionManager(ApplicationContext context) { 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() { @@ -98,29 +73,8 @@ public ObjectMapperSupplier objectMapperSupplier() { @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 - public HandlerRegistry defaultHandlerRegistry() { - return HandlerRegistry.register(); + public ObjectMapper defaultReactiveCommonsObjectMapper(ObjectMapperSupplier supplier) { + return supplier.get(); } @Bean diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java new file mode 100644 index 00000000..96bdc138 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java @@ -0,0 +1,70 @@ +package org.reactivecommons.async.starter.config; + +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.ext.CustomReporter; +import org.reactivecommons.async.commons.ext.DefaultCustomReporter; +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.Configuration; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@Log +@Configuration +@RequiredArgsConstructor +public class ReactiveCommonsListenersConfig { + + @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 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 + public HandlerRegistry defaultHandlerRegistry() { + return HandlerRegistry.register(); + } +} 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 index 7690d131..80f1dbc6 100644 --- 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 @@ -2,15 +2,16 @@ 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.ConnectionManager; import org.reactivecommons.async.starter.config.DomainHandlers; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration -@Import(ReactiveCommonsConfig.class) +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) public class CommandsListenerConfig extends AbstractListenerConfig { public CommandsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { 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 index be7d075e..6e465f84 100644 --- 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 @@ -6,11 +6,12 @@ import org.reactivecommons.async.starter.broker.BrokerProvider; import org.reactivecommons.async.starter.config.DomainHandlers; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration -@Import(ReactiveCommonsConfig.class) +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) public class EventsListenerConfig extends AbstractListenerConfig { public EventsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { 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 index 647f7994..cae527fe 100644 --- 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 @@ -6,11 +6,12 @@ import org.reactivecommons.async.starter.broker.BrokerProvider; import org.reactivecommons.async.starter.config.DomainHandlers; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration -@Import(ReactiveCommonsConfig.class) +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) public class NotificationEventsListenerConfig extends AbstractListenerConfig { public NotificationEventsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { 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 index 9710c770..35ed0f21 100644 --- 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 @@ -2,15 +2,16 @@ 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.ConnectionManager; import org.reactivecommons.async.starter.config.DomainHandlers; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration -@Import(ReactiveCommonsConfig.class) +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) public class QueriesListenerConfig extends AbstractListenerConfig { public QueriesListenerConfig(ConnectionManager manager, DomainHandlers handlers) { 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 index df6efea3..e1654baf 100644 --- 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 @@ -4,7 +4,6 @@ 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; @@ -20,10 +19,10 @@ public class DirectAsyncGatewayConfig { @Bean - public DirectAsyncGateway genericDirectAsyncGateway(ConnectionManager manager, DomainHandlers handlers) { + public DirectAsyncGateway genericDirectAsyncGateway(ConnectionManager manager) { ConcurrentMap directAsyncGateways = new ConcurrentHashMap<>(); manager.forDomain((domain, provider) -> directAsyncGateways.put(domain, - provider.getDirectAsyncGateway(handlers.get(domain)))); + provider.getDirectAsyncGateway())); return new GenericDirectAsyncGateway(directAsyncGateways); } } 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 index f9c0365a..57f27e66 100644 --- 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 @@ -27,7 +27,7 @@ public DomainEventBus getDomainBus() { } @Override - public DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver) { + public DirectAsyncGateway getDirectAsyncGateway() { return null; } @@ -52,7 +52,7 @@ public void listenQueries(HandlerResolver resolver) { } @Override - public void listenReplies(HandlerResolver resolver) { + public void listenReplies() { // for testing purposes } 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 index 6a02949c..506f619d 100644 --- 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 @@ -9,10 +9,8 @@ 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; @@ -40,14 +38,11 @@ void setUp() { @Test void shouldCreateAllDomainEventBuses() { // Arrange - when(brokerProvider.getDirectAsyncGateway(any())).thenReturn(domainEventBus); - DomainHandlers handlers = new DomainHandlers(); - handlers.add("domain", resolver); - handlers.add("domain2", resolver); + when(brokerProvider.getDirectAsyncGateway()).thenReturn(domainEventBus); // Act - DirectAsyncGateway genericDomainEventBus = directAsyncGatewayConfig.genericDirectAsyncGateway(manager, handlers); + DirectAsyncGateway genericDomainEventBus = directAsyncGatewayConfig.genericDirectAsyncGateway(manager); // Assert assertNotNull(genericDomainEventBus); - verify(brokerProvider, times(2)).getDirectAsyncGateway(resolver); + verify(brokerProvider, times(2)).getDirectAsyncGateway(); } } 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 index ea3b6732..f5d04458 100644 --- 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 @@ -46,7 +46,7 @@ public DomainEventBus getDomainBus() { } @Override - public DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver) { + public DirectAsyncGateway getDirectAsyncGateway() { return new KafkaDirectAsyncGateway(); } @@ -95,7 +95,7 @@ public void listenQueries(HandlerResolver resolver) { } @Override - public void listenReplies(HandlerResolver resolver) { + public void listenReplies() { // May be implemented in the future } 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 index 0db6bd4c..0f6f6680 100644 --- 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 @@ -99,7 +99,7 @@ void shouldCreateDomainEventBus() { @Test void shouldCreateDirectAsyncGateway() { // Act - DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(handlerResolver); + DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(); // Assert assertThat(domainBus).isExactlyInstanceOf(KafkaDirectAsyncGateway.class); } 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 index d9fcac5b..9fb2ddb4 100644 --- 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 @@ -7,6 +7,7 @@ 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.config.ReactiveCommonsListenersConfig; import org.reactivecommons.async.starter.impl.kafka.RCKafkaConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,7 +18,9 @@ RCKafkaConfig.class, AsyncKafkaPropsDomain.class, KafkaBrokerProviderFactory.class, - ReactiveCommonsConfig.class}) + ReactiveCommonsConfig.class, + ReactiveCommonsListenersConfig.class +}) class KafkaConfigTest { @Autowired private KafkaJacksonMessageConverter converter; 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 index b116ced6..3abf1e90 100644 --- 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 @@ -53,12 +53,12 @@ public DomainEventBus getDomainBus() { } @Override - public DirectAsyncGateway getDirectAsyncGateway(HandlerResolver resolver) { + public DirectAsyncGateway getDirectAsyncGateway() { String exchangeName = props.getBrokerConfigProps().getDirectMessagesExchangeName(); if (props.getCreateTopology()) { sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("direct")).subscribe(); } - listenReplies(resolver); + listenReplies(); return new RabbitDirectAsyncGateway(config, router, sender, exchangeName, converter, meterRegistry); } @@ -141,7 +141,7 @@ public void listenQueries(HandlerResolver resolver) { } @Override - public void listenReplies(HandlerResolver resolver) { + public void listenReplies() { if (props.isListenReplies()) { final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, 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 index c97c51f5..18be2b17 100644 --- 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 @@ -150,8 +150,8 @@ private static Mono createConnectionMono(ConnectionFactory factory, String connectionType) { return Mono.fromCallable(() -> factory.newConnection(connectionPrefix + " " + connectionType)) .doOnError(err -> - log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host" + - factory.getHost() + ". Starting retry process...", err) + log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host '" + + factory.getHost() + "'. Starting retry process...", err) ) .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(START_INTERVAL)) .maxBackoff(Duration.ofMillis(MAX_BACKOFF_INTERVAL))) 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 index cb25344f..5ea55767 100644 --- 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 @@ -112,7 +112,7 @@ void shouldCreateDirectAsyncGateway() { when(listener.getReceiver()).thenReturn(receiver); when(receiver.consumeAutoAck(any(String.class))).thenReturn(Flux.never()); // Act - DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(handlerResolver); + DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(); // Assert assertThat(domainBus).isExactlyInstanceOf(RabbitDirectAsyncGateway.class); } 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 index 240fe4b7..f811c68d 100644 --- 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 @@ -6,6 +6,7 @@ import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivecommons.async.starter.config.ConnectionManager; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -15,7 +16,9 @@ RabbitMQConfig.class, AsyncPropsDomain.class, RabbitMQBrokerProviderFactory.class, - ReactiveCommonsConfig.class}) + ReactiveCommonsConfig.class, + ReactiveCommonsListenersConfig.class +}) class RabbitMQConfigTest { @Autowired private RabbitJacksonMessageConverter converter; From e544b1c5c54c1d5c6fdfcd249b093439f10c4f47 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Wed, 8 Jan 2025 23:27:02 +0000 Subject: [PATCH 32/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8283887..e002cf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.2.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.1) (2025-01-08) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.0...v5.2.1) + +**Merged pull requests:** + +- fix\(circular-dependency\): Extract listener config and remove handler resolver from replies listener [\#129](https://github.com/reactive-commons/reactive-commons-java/pull/129) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.2.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.0) (2024-12-27) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.3...v5.2.0) diff --git a/gradle.properties b/gradle.properties index 1abb9920..1daa73f6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.2.0 +version=5.2.1 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 From 9b30cf855803337dcd4793bd1eaae2907ae6c927 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:38:33 -0500 Subject: [PATCH 33/50] fix(dynamic-registry): move to respective listener bean dependency (#130) * fix(error-reporter): Restore CustomReporter to default rabbit config * fix(imports): Optimize imports --- .../test/DirectGatewayPerfTest.java | 2 +- .../reactivecommons/test/DummyMessage.java | 1 + .../test/DynamicRegistryTest.java | 1 - .../test/SimpleDirectCommunicationTest.java | 4 +-- .../test/SimpleEventNotificationTest.java | 5 ++-- .../perf/BlockingCommandHandlePerfTest.java | 3 +- ...allelOnBlockingInSubscriptionTimeTest.java | 1 - .../perf/SimpleCommandHandlePerfTest.java | 1 - .../async/api/DynamicRegistry.java | 1 - .../json/JacksonMessageConverter.java | 1 - .../commons/utils/NameGeneratorTest.java | 4 ++- .../KeyMatcherPerformanceManualTest.java | 7 ++++- .../ApplicationNotificationsListener.java | 1 - .../topology/KafkaCustomizationsTest.java | 2 -- .../listeners/GenericMessageListenerTest.java | 2 -- .../async/rabbit/DynamicRegistryImp.java | 4 +-- .../listeners/ApplicationCommandListener.java | 2 +- .../async/helpers/TestStubs.java | 6 ++-- .../async/rabbit/DynamicRegistryImpTest.java | 5 +++- .../async/rabbit/QueryExecutorTest.java | 4 +-- .../async/rabbit/RabbitMessageTest.java | 1 - .../GenericMessageListenerPerfTest.java | 23 +++++++++++++-- .../java/sample/SampleRestController.java | 2 -- .../src/main/java/sample/HandlersConfig.java | 1 - .../java/sample/SampleRestController.java | 3 -- .../starter/config/ReactiveCommonsConfig.java | 10 ++++++- .../ReactiveCommonsListenersConfig.java | 10 ++----- .../listeners/EventsListenerConfig.java | 2 +- .../NotificationEventsListenerConfig.java | 2 +- .../mybroker/MyBrokerSecretFiller.java | 2 +- .../props/MyBrokerAsyncPropsDomain.java | 2 +- .../{ => common}/kafka/RCKafkaConfig.java | 2 +- .../KafkaReactiveHealthIndicatorTest.java | 3 -- .../kafka}/KafkaConfigTest.java | 3 +- .../config/RabbitPropertiesAutoConfig.java | 1 - .../{ => common}/rabbit/RabbitMQConfig.java | 24 ++------------- .../rabbit/RabbitMQListenerOnlyConfig.java | 29 +++++++++++++++++++ .../rabbit/RabbitMQConfigTest.java | 2 +- 38 files changed, 100 insertions(+), 79 deletions(-) rename starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/{ => common}/kafka/RCKafkaConfig.java (97%) rename starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/{rabbit => common/kafka}/KafkaConfigTest.java (92%) rename starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/{ => common}/rabbit/RabbitMQConfig.java (60%) create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java rename starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/{ => common}/rabbit/RabbitMQConfigTest.java (95%) diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java index b312eef5..0e4ade8c 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java @@ -3,8 +3,8 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; +import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java index a36f3cff..d2445516 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java @@ -1,6 +1,7 @@ package org.reactivecommons.test; import lombok.Data; + import java.util.concurrent.ThreadLocalRandom; @Data diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java index cb56377a..d5a94a4e 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java @@ -5,7 +5,6 @@ import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DynamicRegistry; import org.reactivecommons.async.api.handlers.DomainEventHandler; -import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.reactivestreams.Publisher; diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java index d8e26034..d8aa74cf 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java @@ -5,7 +5,6 @@ import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; @@ -23,7 +22,8 @@ import java.util.concurrent.ThreadLocalRandom; import static org.assertj.core.api.Assertions.assertThat; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.empty; +import static reactor.core.publisher.Mono.just; @SpringBootTest class SimpleDirectCommunicationTest { diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java index e700f1ab..e8c8c313 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java @@ -5,7 +5,6 @@ import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.DomainEventHandler; -import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -19,7 +18,9 @@ import java.util.concurrent.ThreadLocalRandom; import static org.assertj.core.api.Assertions.assertThat; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.empty; +import static reactor.core.publisher.Mono.from; +import static reactor.core.publisher.Mono.just; @SpringBootTest class SimpleEventNotificationTest { diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java index aa602602..df853929 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java @@ -4,7 +4,6 @@ import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; @@ -24,7 +23,7 @@ import static java.lang.System.out; import static org.assertj.core.api.Assertions.assertThat; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.fromRunnable; @SpringBootTest class BlockingCommandHandlePerfTest { diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java index 5d578c1a..510c38f6 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java @@ -5,7 +5,6 @@ import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java index fc9b4f50..f5c37146 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java @@ -5,7 +5,6 @@ import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java index 39ebe722..e24de8d3 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java @@ -1,7 +1,6 @@ package org.reactivecommons.async.api; import org.reactivecommons.async.api.handlers.DomainEventHandler; -import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import reactor.core.publisher.Mono; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java index 5d0cbd70..92e8bc72 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; -import io.cloudevents.jackson.JsonFormat; import lombok.Data; import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.Command; diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java index 47b373c5..8a19553d 100644 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java @@ -2,7 +2,9 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class NameGeneratorTest { diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java index d93d069b..c07c9ea9 100755 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java @@ -8,7 +8,12 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; class KeyMatcherPerformanceManualTest { diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java index 9758462f..2b0170e8 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java @@ -14,7 +14,6 @@ import reactor.core.publisher.Mono; import reactor.kafka.receiver.ReceiverRecord; -import java.util.Optional; import java.util.UUID; import java.util.function.Function; diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java index 6b5b035e..2f8dcb41 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java @@ -2,8 +2,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; -import org.reactivecommons.async.kafka.communications.topology.TopicCustomization; import java.util.HashMap; import java.util.Map; diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java index 1025ad60..83dbdeb9 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java @@ -15,7 +15,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoSink; -import reactor.kafka.receiver.ReceiverOffset; import reactor.kafka.receiver.ReceiverRecord; import reactor.test.StepVerifier; @@ -26,7 +25,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java index 608cc099..8e22c31a 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.DynamicRegistry; import org.reactivecommons.async.api.handlers.DomainEventHandler; -import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; @@ -33,7 +32,8 @@ public Mono listenEvent(String eventName, DomainEventHandler fn, Cl @Override public void serveQuery(String resource, QueryHandler handler, Class queryClass) { - resolver.addQueryHandler(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass)); + resolver.addQueryHandler(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message) + , queryClass)); } @Override diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java index 70af3dc3..2e04b22f 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java @@ -8,10 +8,10 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.commons.CommandExecutor; import org.reactivecommons.async.commons.DiscardNotifier; +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.RabbitMessage; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java index 028b0b88..6736ed1a 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java @@ -1,9 +1,11 @@ package org.reactivecommons.async.helpers; -import org.reactivecommons.async.rabbit.RabbitMessage; import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.rabbit.RabbitMessage; -import static org.reactivecommons.async.commons.Headers.*; +import static org.reactivecommons.async.commons.Headers.CORRELATION_ID; +import static org.reactivecommons.async.commons.Headers.REPLY_ID; +import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; public class TestStubs { diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java index 4ae881eb..111c63c6 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java @@ -23,7 +23,10 @@ import java.util.concurrent.ConcurrentHashMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static reactor.core.publisher.Mono.just; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java index 0324dc06..777f43aa 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java @@ -8,10 +8,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.api.From; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.commons.QueryExecutor; +import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.helpers.SampleClass; import org.reactivecommons.async.helpers.TestStubs; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.QueryExecutor; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java index c7b48327..536cb968 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java @@ -4,7 +4,6 @@ import com.rabbitmq.client.Delivery; import com.rabbitmq.client.Envelope; import org.junit.jupiter.api.Test; -import org.reactivecommons.async.rabbit.RabbitMessage; import java.util.HashMap; import java.util.Map; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java index 7f0f40c4..f7dd0377 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java @@ -2,7 +2,24 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.rabbitmq.client.*; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.CancelCallback; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.ConfirmCallback; +import com.rabbitmq.client.ConfirmListener; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.Consumer; +import com.rabbitmq.client.ConsumerShutdownSignalCallback; +import com.rabbitmq.client.DeliverCallback; +import com.rabbitmq.client.Delivery; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.Method; +import com.rabbitmq.client.ReturnCallback; +import com.rabbitmq.client.ReturnListener; +import com.rabbitmq.client.ShutdownListener; +import com.rabbitmq.client.ShutdownSignalException; import lombok.Data; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -12,11 +29,11 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.Command; -import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.communications.Message; +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.commons.ext.CustomReporter; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.AcknowledgableDelivery; diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java index 98cf4009..27c11203 100644 --- a/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java @@ -2,14 +2,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.cloudevents.CloudEvent; -import io.cloudevents.CloudEventData; import io.cloudevents.core.builder.CloudEventBuilder; import io.cloudevents.jackson.JsonCloudEventData; import lombok.Data; import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java b/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java index 5f4807dd..75f79669 100644 --- a/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java +++ b/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java @@ -4,7 +4,6 @@ import org.reactivecommons.async.api.DynamicRegistry; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.DomainEventHandler; -import org.reactivecommons.async.api.handlers.EventHandler; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java index a2087551..00e060c3 100644 --- a/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java +++ b/samples/async/async-sender-client/src/main/java/sample/SampleRestController.java @@ -1,6 +1,5 @@ package sample; -import io.cloudevents.CloudEvent; import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; @@ -23,8 +22,6 @@ import sample.model.broker.AnimalEvent; import sample.model.broker.RemovedMemberEvent; -import java.net.URI; -import java.time.OffsetDateTime; import java.util.UUID; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 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 index 1b612fdc..fdb02d8d 100644 --- 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 @@ -8,6 +8,8 @@ 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; @@ -30,7 +32,7 @@ @Configuration @RequiredArgsConstructor @Import(ReactiveCommonsHealthConfig.class) -@ComponentScan("org.reactivecommons.async.starter.impl") +@ComponentScan("org.reactivecommons.async.starter.impl.common") public class ReactiveCommonsConfig { @Bean @@ -89,4 +91,10 @@ public MeterRegistry defaultRabbitMeterRegistry() { return new SimpleMeterRegistry(); } + @Bean + @ConditionalOnMissingBean + public CustomReporter reactiveCommonsCustomErrorReporter() { + return new DefaultCustomReporter(); + } + } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java index 96bdc138..44cc8772 100644 --- a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java @@ -7,12 +7,11 @@ import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.HandlerResolverBuilder; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.commons.ext.DefaultCustomReporter; 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 reactor.core.publisher.Mono; @@ -21,6 +20,7 @@ @Log @Configuration @RequiredArgsConstructor +@ComponentScan("org.reactivecommons.async.starter.impl.listener") public class ReactiveCommonsListenersConfig { @Bean @@ -41,12 +41,6 @@ public DomainHandlers buildHandlers(ApplicationContext context, return handlers; } - @Bean - @ConditionalOnMissingBean - public CustomReporter reactiveCommonsCustomErrorReporter() { - return new DefaultCustomReporter(); - } - @Bean @ConditionalOnMissingBean @SuppressWarnings("rawtypes") 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 index 6e465f84..3e31d9fa 100644 --- 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 @@ -2,8 +2,8 @@ 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.ConnectionManager; import org.reactivecommons.async.starter.config.DomainHandlers; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; 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 index cae527fe..8e918915 100644 --- 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 @@ -2,8 +2,8 @@ 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.ConnectionManager; import org.reactivecommons.async.starter.config.DomainHandlers; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; 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 index 2af3a2e8..29a8a841 100644 --- 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 @@ -1,7 +1,7 @@ package org.reactivecommons.async.starter.mybroker; -import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; 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/MyBrokerAsyncPropsDomain.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java index d2239eb0..fa348806 100644 --- 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 @@ -2,8 +2,8 @@ import lombok.Getter; import lombok.Setter; -import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; import org.springframework.beans.factory.annotation.Value; @Getter 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/common/kafka/RCKafkaConfig.java similarity index 97% rename from starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/kafka/RCKafkaConfig.java rename to starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/common/kafka/RCKafkaConfig.java index 3b830911..e0749722 100644 --- 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/common/kafka/RCKafkaConfig.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.starter.impl.kafka; +package org.reactivecommons.async.starter.impl.common.kafka; import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; import org.reactivecommons.async.kafka.KafkaBrokerProviderFactory; 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 index 132bd660..b713a513 100644 --- 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 @@ -10,9 +10,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.starter.config.health.RCHealth; -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; 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/common/kafka/KafkaConfigTest.java similarity index 92% rename from starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/KafkaConfigTest.java rename to starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/common/kafka/KafkaConfigTest.java index 9fb2ddb4..25363d28 100644 --- 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/common/kafka/KafkaConfigTest.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.starter.impl.rabbit; +package org.reactivecommons.async.starter.impl.common.kafka; import org.junit.jupiter.api.Test; @@ -8,7 +8,6 @@ import org.reactivecommons.async.starter.config.ConnectionManager; import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; -import org.reactivecommons.async.starter.impl.kafka.RCKafkaConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; 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 e05cd797..399b6c20 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 @@ -2,7 +2,6 @@ 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/starter/impl/rabbit/RabbitMQConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfig.java similarity index 60% rename from starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfig.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfig.java index 50526cb3..1345923e 100644 --- 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/common/rabbit/RabbitMQConfig.java @@ -1,30 +1,20 @@ -package org.reactivecommons.async.starter.impl.rabbit; +package org.reactivecommons.async.starter.impl.common.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 @@ -38,15 +28,6 @@ public RabbitJacksonMessageConverter messageConverter(ObjectMapperSupplier objec 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() { @@ -56,7 +37,8 @@ public AsyncPropsDomain.RabbitSecretFiller defaultRabbitSecretFiller() { @Bean @ConditionalOnMissingBean(RabbitProperties.class) - public RabbitProperties defaultRabbitProperties(RabbitPropertiesAutoConfig properties, ObjectMapperSupplier supplier) { + 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/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java new file mode 100644 index 00000000..cebe207e --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.impl.listener.rabbit; + +import org.reactivecommons.async.api.DynamicRegistry; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.rabbit.DynamicRegistryImp; +import org.reactivecommons.async.rabbit.RabbitMQSetupUtils; +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.BrokerConfigProps; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@Configuration +public class RabbitMQListenerOnlyConfig { + + @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); + } +} 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/common/rabbit/RabbitMQConfigTest.java similarity index 95% rename from starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/rabbit/RabbitMQConfigTest.java rename to starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfigTest.java index f811c68d..29b0f364 100644 --- 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/common/rabbit/RabbitMQConfigTest.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.starter.impl.rabbit; +package org.reactivecommons.async.starter.impl.common.rabbit; import org.junit.jupiter.api.Test; import org.reactivecommons.async.rabbit.RabbitMQBrokerProviderFactory; From 46f7d77c790190133efb320c63cde2fa5b489edf Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Thu, 9 Jan 2025 19:45:02 +0000 Subject: [PATCH 34/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e002cf31..05ed514c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.2.2](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.2) (2025-01-09) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.1...v5.2.2) + +**Merged pull requests:** + +- fix\(dynamic-registry\): move to respective listener bean dependency [\#130](https://github.com/reactive-commons/reactive-commons-java/pull/130) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.2.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.1) (2025-01-08) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.0...v5.2.1) diff --git a/gradle.properties b/gradle.properties index 1daa73f6..efe11b62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.2.1 +version=5.2.2 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 From 595f3e8e84169adf165fadffd139f4804ae529ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:33:23 -0500 Subject: [PATCH 35/50] build(deps): bump path-to-regexp and express in /docs (#132) Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `path-to-regexp` from 0.1.10 to 1.9.0 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v1.9.0) Updates `express` from 4.21.1 to 4.21.2 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index f319895a..5e6f9577 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -7256,9 +7256,10 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7279,7 +7280,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -7294,6 +7295,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/content-disposition": { @@ -7321,9 +7326,10 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", From 454e18d9afff0e56ad57d4eb4ffd7a9d1c46e680 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:33:38 -0500 Subject: [PATCH 36/50] docs(updated): Add secret example documentation and updater actions (#131) * docs(updated): Add secret example documentation and updater actions --- .github/workflows/updater.yml | 41 +++++++++++ build.gradle | 2 +- .../reactive-commons/1-getting-started.md | 3 + .../9-configuration-properties.md | 71 +++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/updater.yml diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml new file mode 100644 index 00000000..1f88ecd6 --- /dev/null +++ b/.github/workflows/updater.yml @@ -0,0 +1,41 @@ +name: updater +on: + workflow_dispatch: + schedule: + - cron: '0 12 * * 5' # every Friday at 07:00 Colombia Time +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + - name: Check for updates + run: ./gradlew internalTask --action UPDATE_DEPENDENCIES + - name: Check for changes + id: git_changes + run: | + git diff --name-only + if [[ $(git diff --name-only) ]]; then + echo "Changes detected!" + echo "HAS_CHANGES=true" >> $GITHUB_ENV + else + echo "No changes detected!" + echo "HAS_CHANGES=false" >> $GITHUB_ENV + fi + - name: Create Pull Request + if: env.HAS_CHANGES == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.PA_TOKEN }} + committer: Dependencies Bot + commit-message: 'fix(deps): update dependencies' + title: 'fix(deps): update dependencies' + body: 'This PR updates dependencies to latest versions' + branch: 'feature/autoupdate-deps' + base: 'master' + labels: 'dependencies' + reviewers: 'juancgalvis' \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8ebc5ddf..a0c0bb7e 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { id 'org.sonarqube' version '6.0.1.5171' id 'org.springframework.boot' version '3.4.1' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.20.8' + id 'co.com.bancolombia.cleanArchitecture' version '3.20.12' } repositories { diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index d8985c64..4d400ca8 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -112,6 +112,9 @@ public class MyRabbitMQConfig { } ``` +Please refer to [Configuration Properties](/reactive-commons-java/docs/reactive-commons/configuration-properties) +Or with secrets [Loading properties from a secret](/reactive-commons-java/docs/reactive-commons/configuration-properties#loading-properties-from-a-secret) + The 5.x.x stable version of Reactive Commons has been merged with the deprecated `-eda` variant, this means that the `async-commons-rabbit-starter` artifact is now the only one to use. diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index d863d124..a0da9bd8 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -105,6 +105,8 @@ public class MyDomainConfig { } ``` +## Loading properties from a secret + Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.RabbitSecretFiller` class. ```java @@ -115,6 +117,75 @@ public AsyncPropsDomain.RabbitSecretFiller customFiller() { // customize asyncProps here by domain }; } +``` + +For example if you use the [Secrets Manager](https://github.com/bancolombia/secrets-manager) project, you may use +the next code to load the properties from a secret: + +1. Create a class with the properties that you will load from the secret: +```java +import lombok.Builder; +import lombok.Data; +import org.reactivecommons.async.rabbit.config.RabbitProperties; + +@Data +@Builder +public class RabbitConnectionProperties { + private String hostname; + private String password; + private String username; + private Integer port; + private String virtualhost; + private boolean ssl; + + public RabbitProperties toRabbitProperties() { + var rabbitProperties = new RabbitProperties(); + rabbitProperties.setHost(this.hostname); + rabbitProperties.setUsername(this.username); + rabbitProperties.setPassword(this.password); + rabbitProperties.setPort(this.port); + rabbitProperties.setVirtualHost(this.virtualhost); + rabbitProperties.getSsl().setEnabled(this.ssl); // To enable SSL + return rabbitProperties; + } +} +``` +2. Use the `SecretsManager` to load the properties from the secret: + +```java +import co.com.bancolombia.secretsmanager.api.GenericManager; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.lang.reflect.GenericArrayType; + +@Log4j2 +@Configuration +public class AsyncEventBusConfig { + + // TODO: You should create the GenericManager bean as indicated in Secrets Manager library + @Bean + public AsyncPropsDomain.RabbitSecretFiller rabbitSecretFiller(GenericManager genericManager) { + return (domain, props) -> { + if (props.getSecret() != null) { + log.info("Loading RabbitMQ connection properties from secret: {}", props.getSecret()); + props.setConnectionProperties(getFromSecret(genericManager, props.getSecret())); + log.info("RabbitMQ connection properties loaded successfully with host: '{}'", + props.getConnectionProperties().getHost()); + } + }; + } + + @SneakyThrows + private RabbitProperties getFromSecret(GenericManager genericManager, String secretName) { + return genericManager.getSecret(secretName, RabbitConnectionProperties.class).toRabbitProperties(); + } +} + ``` From 15ee39fc54acd59f2a7d42623a6682a3655d7e57 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:58:56 -0500 Subject: [PATCH 37/50] fix(deps): update dependencies (#133) --- build.gradle | 4 ++-- main.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index a0c0bb7e..4a898c00 100644 --- a/build.gradle +++ b/build.gradle @@ -13,9 +13,9 @@ buildscript { plugins { id 'jacoco' id 'org.sonarqube' version '6.0.1.5171' - id 'org.springframework.boot' version '3.4.1' apply false + id 'org.springframework.boot' version '3.4.2' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.20.12' + id 'co.com.bancolombia.cleanArchitecture' version '3.20.13' } repositories { diff --git a/main.gradle b/main.gradle index 5796af1d..66cffb97 100644 --- a/main.gradle +++ b/main.gradle @@ -80,7 +80,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.1' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.2' } } From ae1f5ec4f86acc5c469afd09809e822453589099 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Fri, 24 Jan 2025 20:05:46 +0000 Subject: [PATCH 38/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 25 +++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ed514c..72c03baf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [v5.2.3](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.3) (2025-01-24) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.2...v5.2.3) + +**Implemented enhancements:** + +- Add unit tests for classes \(RabbitDomainEventBus, RabbitDiscardNotifier\) [\#75](https://github.com/reactive-commons/reactive-commons-java/issues/75) +- Test de class TopologyCreator of module async-rabbit [\#74](https://github.com/reactive-commons/reactive-commons-java/issues/74) +- Increase coverage by testing classes \(QueryExecutor, EventExecutor and CommandExecutor\) [\#72](https://github.com/reactive-commons/reactive-commons-java/issues/72) +- Centralize bintray task configuration in a single place [\#8](https://github.com/reactive-commons/reactive-commons-java/issues/8) + +**Closed issues:** + +- Acerca de Reactive Commons y Secretos AWS Secrets Manager [\#81](https://github.com/reactive-commons/reactive-commons-java/issues/81) +- Allow multi broker configuration [\#80](https://github.com/reactive-commons/reactive-commons-java/issues/80) +- Update DynamicRegistryImp reactive commons SQS to new DynamicRegistry especification [\#78](https://github.com/reactive-commons/reactive-commons-java/issues/78) +- Add interaction diagrams to documentation [\#76](https://github.com/reactive-commons/reactive-commons-java/issues/76) +- Circular dependency with HandlerResolver and Dynamic registry [\#55](https://github.com/reactive-commons/reactive-commons-java/issues/55) + +**Merged pull requests:** + +- fix\(deps\): update dependencies [\#133](https://github.com/reactive-commons/reactive-commons-java/pull/133) ([juancgalvis](https://github.com/juancgalvis)) +- build\(deps\): bump path-to-regexp and express in /docs [\#132](https://github.com/reactive-commons/reactive-commons-java/pull/132) ([dependabot[bot]](https://github.com/apps/dependabot)) +- docs\(updated\): Add secret example documentation and updater actions [\#131](https://github.com/reactive-commons/reactive-commons-java/pull/131) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.2.2](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.2) (2025-01-09) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.1...v5.2.2) diff --git a/gradle.properties b/gradle.properties index efe11b62..a8015b09 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.2.2 +version=5.2.3 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 From 4235ab861db64d67f5e1458a5f6fd359801f0b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Guillermo=20G=C3=B3mez=20Galeano?= <44306301+luisgomez29@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:28:29 -0500 Subject: [PATCH 39/50] refactor: fix code semells (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: fix code smells * docs: fix rabbitmq docs * refactor: convert message body to string --------- Co-authored-by: luisgomez29 --- .../test/CommandsProcessPerfTest.java | 15 +- .../test/DirectGatewayPerfTest.java | 20 +- .../test/DynamicRegistryTest.java | 14 +- .../test/QueryProcessPerfTest.java | 23 +- .../test/SimpleDirectCommunicationTest.java | 35 +- .../test/SimpleEventNotificationTest.java | 22 +- .../perf/BlockingCommandHandlePerfTest.java | 46 +- ...allelOnBlockingInSubscriptionTimeTest.java | 33 +- .../perf/SimpleCommandHandlePerfTest.java | 28 +- .../async/api/DirectAsyncGateway.java | 2 +- .../async/api/HandlerRegistry.java | 19 +- .../async/api/HandlerRegistryTest.java | 42 +- .../async/commons/CommandExecutor.java | 4 +- .../async/commons/DLQDiscardNotifier.java | 4 +- .../async/commons/EventExecutor.java | 4 +- .../async/commons/FallbackStrategy.java | 6 +- .../async/commons/HandlerResolver.java | 5 +- .../async/commons/HandlerResolverBuilder.java | 41 +- .../async/commons/Headers.java | 10 +- .../async/commons/config/BrokerConfig.java | 11 +- .../converters/json/CloudEventBuilderExt.java | 7 +- .../async/commons/ext/CustomReporter.java | 24 +- .../async/commons/utils/ArrayUtils.java | 7 +- .../async/commons/utils/LoggerSubscriber.java | 6 +- .../async/commons/utils/NameGenerator.java | 8 +- .../utils/resolver/HandlerResolverUtil.java | 7 +- .../reply/ReactiveReplyRouterTest.java | 10 +- .../KeyMatcherPerformanceManualTest.java | 5 +- .../KeyMatcherPerformanceWildcardTest.java | 11 +- .../async/kafka/KafkaDomainEventBus.java | 4 +- .../async/kafka/KafkaMessage.java | 14 +- .../ReactiveMessageListener.java | 4 +- .../communications/ReactiveMessageSender.java | 26 +- .../topology/TopologyCreator.java | 9 +- .../listeners/GenericMessageListener.java | 16 +- .../kafka/KafkaDirectAsyncGatewayTest.java | 50 +- .../async/kafka/KafkaMessageTest.java | 22 +- .../topology/KafkaCustomizationsTest.java | 2 +- .../listeners/GenericMessageListenerTest.java | 15 +- .../async/rabbit/DynamicRegistryImp.java | 5 +- .../communications/ReactiveMessageSender.java | 77 +-- .../communications/TopologyCreator.java | 6 +- .../listeners/ApplicationCommandListener.java | 39 +- .../listeners/ApplicationEventListener.java | 42 +- .../ApplicationNotificationListener.java | 28 +- .../listeners/ApplicationQueryListener.java | 38 +- .../listeners/GenericMessageListener.java | 21 +- .../async/helpers/TestStubs.java | 4 +- .../async/rabbit/HandlerResolverTest.java | 24 +- .../rabbit/RabbitDirectAsyncGatewayTest.java | 22 +- .../async/rabbit/RabbitMessageTest.java | 4 +- .../ReactiveMessageSenderTest.java | 10 +- .../json/CloudEventBuilderExtTest.java | 7 +- .../json/JacksonMessageConverterTest.java | 4 +- .../ApplicationCommandListenerPerfTest.java | 79 +-- .../ApplicationCommandListenerTest.java | 33 +- .../ApplicationEventListenerTest.java | 25 +- .../ApplicationNotificationListenerTest.java | 22 +- .../ApplicationQueryListenerErrorTest.java | 16 +- .../GenericMessageListenerPerfTest.java | 41 +- .../ListenerReporterTestSuperClass.java | 54 +- .../async/utils/TestUtils.java | 7 +- build.gradle | 4 +- .../2-sending-a-domain-event.md | 2 +- .../4-making-an-async-query.md | 2 +- .../8-serving-async-queries.md | 6 +- .../9-configuration-properties.md | 6 +- docs/docs/scenarios/1-single-broker.md | 10 +- .../docs/scenarios/2-two-brokers-same-type.md | 8 +- .../reactivecommons/api/domain/Command.java | 4 +- .../api/domain/DomainEvent.java | 4 +- .../api/domain/DomainEventBus.java | 2 + gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 3 +- main.gradle | 18 +- .../ReactiveCommonsListenersConfig.java | 14 +- .../async/starter/config/health/RCHealth.java | 4 +- .../config/health/RCHealthIndicator.java | 4 +- .../ReactiveCommonsHealthIndicator.java | 8 +- .../starter/mybroker/MyBrokerProvider.java | 4 +- .../props/GenericAsyncPropsDomainTest.java | 9 +- .../async/kafka/KafkaBrokerProvider.java | 4 +- .../kafka/KafkaBrokerProviderFactory.java | 7 +- .../async/kafka/KafkaDiscardProvider.java | 4 +- .../async/kafka/KafkaSetupUtils.java | 14 +- .../kafka/config/props/AsyncKafkaProps.java | 1 + .../health/KafkaReactiveHealthIndicator.java | 4 +- .../config/DirectAsyncGatewayConfig.java | 4 +- .../standalone/config/EventBusConfig.java | 9 +- .../standalone/config/RabbitMqConfig.java | 14 +- .../async/rabbit/RabbitMQBrokerProvider.java | 16 +- .../rabbit/RabbitMQBrokerProviderFactory.java | 4 +- .../async/rabbit/RabbitMQDiscardProvider.java | 4 +- .../async/rabbit/RabbitMQSetupUtils.java | 9 +- .../AsyncRabbitPropsDomainProperties.java | 6 +- .../config/props/BrokerConfigProps.java | 3 +- .../config/spring/RabbitPropertiesBase.java | 466 ++---------------- .../rabbit/RabbitMQBrokerProviderTest.java | 39 +- 98 files changed, 923 insertions(+), 1038 deletions(-) diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java index d1afdf0d..c4f974a1 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java @@ -27,7 +27,7 @@ class CommandsProcessPerfTest { private static final String COMMAND_NAME = "app.command.test"; - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private static final Semaphore semaphore = new Semaphore(0); private static final CountDownLatch latch = new CountDownLatch(12 + 1); @@ -41,18 +41,18 @@ class CommandsProcessPerfTest { @Test void commandShouldArrive() throws InterruptedException { final long init_p = System.currentTimeMillis(); - createMessages(messageCount); + createMessages(MESSAGE_COUNT); final long end_p = System.currentTimeMillis() - init_p; System.out.println("Total Publication Time: " + end_p + "ms"); latch.countDown(); final long init = System.currentTimeMillis(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - final double microsPerMessage = ((total + 0.0) / messageCount) * 1000; - System.out.println("Message count: " + messageCount); + final double microsPerMessage = ((total + 0.0) / MESSAGE_COUNT) * 1000; + System.out.println("Message count: " + MESSAGE_COUNT); System.out.println("Total Execution Time: " + total + "ms"); System.out.println("Microseconds per message: " + microsPerMessage + "us"); if (System.getProperty("env.ci") == null) { @@ -82,7 +82,10 @@ public static void main(String[] args) { @Bean public HandlerRegistry registry() { - final HandlerRegistry registry = range(0, 20).reduce(HandlerRegistry.register(), (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); + final HandlerRegistry registry = range(0, 20) + .reduce(HandlerRegistry.register(), (r, i) -> + r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)) + .block(); return registry .handleCommand(COMMAND_NAME, this::handleSimple, DummyMessage.class); } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java index 0e4ade8c..208277a5 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java @@ -24,7 +24,7 @@ class DirectGatewayPerfTest { private static final String COMMAND_NAME = "app.command.test"; - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private static final Semaphore semaphore = new Semaphore(0); @Autowired @@ -36,17 +36,17 @@ class DirectGatewayPerfTest { @Test void shouldSendInOptimalTime() throws InterruptedException { - final Flux> messages = createMessages(messageCount); + final Flux> messages = createMessages(MESSAGE_COUNT); final Flux target = messages.flatMap(dummyMessageCommand -> gateway.sendCommand(dummyMessageCommand, appName) .doOnSuccess(aVoid -> semaphore.release())); final long init = System.currentTimeMillis(); target.subscribe(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); - assertMessageThroughput(end - init, messageCount, 200); + assertMessageThroughput(end - init, MESSAGE_COUNT, 200); } @Test @@ -67,8 +67,10 @@ void shouldSendBatchInOptimalTime1Channel() throws InterruptedException { private void shouldSendBatchInOptimalTimeNChannels(int channels) throws InterruptedException { List> subs = new ArrayList<>(channels); for (int i = 0; i < channels; ++i) { - final Flux> messages = createMessages(messageCount / channels); - final Mono target = gateway.sendCommands(messages, appName).then().doOnSuccess(_v -> semaphore.release()); + final Flux> messages = createMessages(MESSAGE_COUNT / channels); + final Mono target = gateway.sendCommands(messages, appName) + .then() + .doOnSuccess(_v -> semaphore.release()); subs.add(target); } @@ -79,7 +81,7 @@ private void shouldSendBatchInOptimalTimeNChannels(int channels) throws Interrup final long end = System.currentTimeMillis(); final long total = end - init; - assertMessageThroughput(total, messageCount, 230); + assertMessageThroughput(total, MESSAGE_COUNT, 230); } private void assertMessageThroughput(long total, long messageCount, int reqMicrosPerMessage) { @@ -94,7 +96,9 @@ private void assertMessageThroughput(long total, long messageCount, int reqMicro } private Flux> createMessages(int count) { - final List> commands = IntStream.range(0, count).mapToObj(value -> new Command<>(COMMAND_NAME, UUID.randomUUID().toString(), new DummyMessage())).collect(Collectors.toList()); + final List> commands = IntStream.range(0, count) + .mapToObj(value -> new Command<>(COMMAND_NAME, UUID.randomUUID().toString(), new DummyMessage())) + .collect(Collectors.toList()); return Flux.fromIterable(commands); } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java index d5a94a4e..1d6e380b 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java @@ -13,7 +13,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -35,21 +35,20 @@ class DynamicRegistryTest { @Test void shouldReceiveResponse() { - UnicastProcessor result = UnicastProcessor.create(); - DomainEventHandler fn = message -> fromRunnable(() -> result.onNext(message.getData())); + Sinks.Many result = Sinks.many().unicast().onBackpressureBuffer(); + DomainEventHandler fn = message -> fromRunnable( + () -> result.emitNext(message.getData(), Sinks.EmitFailureHandler.FAIL_FAST) + ); dynamicRegistry.listenEvent("test.event", fn, String.class).block(); final Publisher emit = eventBus.emit(new DomainEvent<>("test.event", "42", "Hello")); from(emit).block(); - StepVerifier.create(result.next().timeout(Duration.ofSeconds(10))) + StepVerifier.create(result.asFlux().next().timeout(Duration.ofSeconds(10))) .expectNext("Hello") .verifyComplete(); - - } - @SpringBootApplication @EnableMessageListeners @EnableDomainEventBus @@ -57,6 +56,5 @@ static class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } - } } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java index 7fafa565..cc85ac89 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java @@ -30,7 +30,7 @@ class QueryProcessPerfTest { private static final String QUERY_NAME = "app.command.test"; - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private static final Semaphore semaphore = new Semaphore(0); private static final AtomicLong atomicLong = new AtomicLong(0); private static final CountDownLatch latch = new CountDownLatch(12 + 1); @@ -44,19 +44,20 @@ class QueryProcessPerfTest { @Test void serveQueryPerformanceTest() throws InterruptedException { - final Flux> messages = createMessages(messageCount); + final Flux> messages = createMessages(MESSAGE_COUNT); final long init = System.currentTimeMillis(); messages - .flatMap(dummyMessageAsyncQuery -> gateway.requestReply(dummyMessageAsyncQuery, appName, DummyMessage.class) - .doOnNext(s -> semaphore.release()) + .flatMap(dummyMessageAsyncQuery -> + gateway.requestReply(dummyMessageAsyncQuery, appName, DummyMessage.class) + .doOnNext(s -> semaphore.release()) ) .subscribe(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - assertMessageThroughput(total, messageCount, 200); + assertMessageThroughput(total, MESSAGE_COUNT, 200); } private void assertMessageThroughput(long total, long messageCount, int reqMicrosPerMessage) { @@ -72,7 +73,9 @@ private void assertMessageThroughput(long total, long messageCount, int reqMicro private Flux> createMessages(int count) { - final List> queryList = IntStream.range(0, count).mapToObj(_v -> new AsyncQuery<>(QUERY_NAME, new DummyMessage())).collect(Collectors.toList()); + final List> queryList = IntStream.range(0, count) + .mapToObj(_v -> new AsyncQuery<>(QUERY_NAME, new DummyMessage())) + .collect(Collectors.toList()); return Flux.fromIterable(queryList); } @@ -87,7 +90,11 @@ public static void main(String[] args) { @Bean public HandlerRegistry registry() { - final HandlerRegistry registry = range(0, 20).reduce(HandlerRegistry.register(), (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); + final HandlerRegistry registry = range(0, 20) + .reduce(HandlerRegistry.register(), (r, i) -> r.handleCommand( + "app.command.name" + i, message -> Mono.empty(), Map.class + )) + .block(); return registry .serveQuery(QUERY_NAME, this::handleSimple, DummyMessage.class); } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java index d8aa74cf..06abd8e8 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java @@ -15,7 +15,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Mono; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -36,20 +36,18 @@ class SimpleDirectCommunicationTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test void commandShouldArrive() { Command command = new Command<>(COMMAND_NAME, commandId, data); gateway.sendCommand(command, appName).subscribe(); + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); - StepVerifier.create(listener.next()).assertNext(cmd -> { + StepVerifier.create(listener.asFlux().next()).assertNext(cmd -> { assertThat(cmd).extracting(Command::getCommandId, Command::getData, Command::getName) - .containsExactly(commandId, data, COMMAND_NAME); + .containsExactly(commandId, data, COMMAND_NAME); }).verifyComplete(); } @@ -57,34 +55,33 @@ void commandShouldArrive() { void shouldReceiveResponse() { final Mono reply = gateway.requestReply(new AsyncQuery<>("double", 42), appName, Integer.class); StepVerifier.create(reply.timeout(Duration.ofSeconds(15))) - .expectNext(42*2) - .verifyComplete(); + .expectNext(42 * 2) + .verifyComplete(); } - @SpringBootApplication @EnableDirectAsyncGateway @EnableMessageListeners - static class App{ + static class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() - .serveQuery("double", rqt -> just(rqt*2), Long.class) - .handleCommand(COMMAND_NAME, handle(listener), Long.class); + .serveQuery("double", rqt -> just(rqt * 2), Long.class) + .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private DomainCommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(Sinks.Many> listener) { return command -> { - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); return empty(); }; } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java index e8c8c313..ff5c7949 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java @@ -12,7 +12,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.util.concurrent.ThreadLocalRandom; @@ -30,17 +30,15 @@ class SimpleEventNotificationTest { @Autowired private DomainEventBus eventBus; - @Autowired - private UnicastProcessor> listener; - - private String eventId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String eventId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test void shouldReceiveEvent() throws InterruptedException { DomainEvent event = new DomainEvent<>(EVENT_NAME, eventId, data); + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); from(eventBus.emit(event)).subscribe(); - StepVerifier.create(listener.take(1)).assertNext(evt -> + StepVerifier.create(listener.asFlux().take(1)).assertNext(evt -> assertThat(evt).extracting(DomainEvent::getName, DomainEvent::getEventId, DomainEvent::getData) .containsExactly(EVENT_NAME, eventId, data) ).verifyComplete(); @@ -56,20 +54,20 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .serveQuery("double", rqt -> just(rqt * 2), Long.class) .listenEvent(EVENT_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private DomainEventHandler handle(UnicastProcessor> listener) { + private DomainEventHandler handle(Sinks.Many> listener) { return command -> { - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); return empty(); }; } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java index df853929..f146ee9a 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java @@ -14,7 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Flux; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -36,14 +36,13 @@ class BlockingCommandHandlePerfTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test void commandShouldBeHandledInParallel() throws InterruptedException { + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); + Flux.range(0, 12).flatMap(i -> { Command command = new Command<>(COMMAND_NAME, commandId + 1, data + 1); return gateway.sendCommand(command, appName); @@ -51,13 +50,13 @@ void commandShouldBeHandledInParallel() throws InterruptedException { final long init = System.currentTimeMillis(); - final Flux> results = listener.take(12).collectList() + final Flux> results = listener.asFlux().take(12).collectList() .timeout(Duration.ofMillis(1500)) .flatMapMany(Flux::fromIterable); StepVerifier.create(results).assertNext(cmd -> { - assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); - }) + assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); + }) .expectNextCount(11) .verifyComplete(); @@ -79,30 +78,25 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private DomainCommandHandler handle(UnicastProcessor> listener) { - return command -> { - return fromRunnable(() -> { -// out.println("Received at: " + System.currentTimeMillis()/1000); - try { -// out.println("internal: " + Thread.currentThread().getName()); - TimeUnit.MILLISECONDS.sleep(750); -// out.println("Handled at: " + System.currentTimeMillis()/1000); - listener.onNext(command); - } catch (InterruptedException e) { - } - listener.onNext(command); - }); - }; + private DomainCommandHandler handle(Sinks.Many> listener) { + return command -> fromRunnable(() -> { + try { + TimeUnit.MILLISECONDS.sleep(750); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); + } catch (InterruptedException ignored) { + } + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); + }); } } } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java index 510c38f6..c2d00722 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java @@ -15,7 +15,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Flux; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -37,15 +37,13 @@ class ParallelOnBlockingInSubscriptionTimeTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test @Disabled - public void commandShouldBeHandledInParallel() throws InterruptedException { + void commandShouldBeHandledInParallel() throws InterruptedException { + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); Flux.range(0, 12).flatMap(i -> { Command command = new Command<>(COMMAND_NAME, commandId + 1, data + 1); return gateway.sendCommand(command, appName); @@ -53,13 +51,13 @@ public void commandShouldBeHandledInParallel() throws InterruptedException { final long init = System.currentTimeMillis(); - final Flux> results = listener.take(12).collectList() + final Flux> results = listener.asFlux().take(12).collectList() .timeout(Duration.ofMillis(1500)) .flatMapMany(Flux::fromIterable); StepVerifier.create(results).assertNext(cmd -> { - assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); - }) + assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); + }) .expectNextCount(11) .verifyComplete(); @@ -81,27 +79,24 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private DomainCommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(Sinks.Many> listener) { return command -> { -// out.println("Received at: " + System.currentTimeMillis()/1000); try { -// out.println("internal: " + Thread.currentThread().getName()); TimeUnit.MILLISECONDS.sleep(750); -// out.println("Handled at: " + System.currentTimeMillis()/1000); - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); } catch (InterruptedException e) { } - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); return empty(); }; } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java index f5c37146..582bf059 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java @@ -15,7 +15,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Flux; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -36,15 +36,13 @@ class SimpleCommandHandlePerfTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test @Disabled - public void commandShouldBeHandledInParallel() { + void commandShouldBeHandledInParallel() { + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); Flux.range(0, 30).flatMap(i -> { Command command = new Command<>(COMMAND_NAME, commandId + 1, data + 1); return gateway.sendCommand(command, appName); @@ -52,13 +50,13 @@ public void commandShouldBeHandledInParallel() { final long init = System.currentTimeMillis(); - final Flux> results = listener.take(30).collectList() + final Flux> results = listener.asFlux().take(30).collectList() .timeout(Duration.ofMillis(3500)) .flatMapMany(Flux::fromIterable); StepVerifier.create(results).assertNext(cmd -> { - assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); - }) + assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); + }) .expectNextCount(29) .verifyComplete(); @@ -78,23 +76,23 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private DomainCommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(Sinks.Many> listener) { return command -> { out.println("Received at: " + System.currentTimeMillis() / 1000); out.println("Received in: " + Thread.currentThread().getName()); return delay(Duration.ofMillis(750)).doOnNext(i -> { out.println("Handled at: " + System.currentTimeMillis() / 1000); - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); }).then(); }; } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java index 661b5349..ee48e05e 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java @@ -5,7 +5,7 @@ import reactor.core.publisher.Mono; public interface DirectAsyncGateway { - public static final String DELAYED = "rc-delay"; + String DELAYED = "rc-delay"; Mono sendCommand(Command command, String targetName); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java index 67ae2a08..f97d2e8c 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java @@ -22,7 +22,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PACKAGE) -public class HandlerRegistry { +public final class HandlerRegistry { public static final String DEFAULT_DOMAIN = "app"; private final Map>> domainEventListeners = new ConcurrentHashMap<>(); private final List> dynamicEventHandlers = new CopyOnWriteArrayList<>(); @@ -37,7 +37,8 @@ public static HandlerRegistry register() { return instance; } - public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, Class eventClass) { + public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, + Class eventClass) { domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) .add(new RegisteredEventListener<>(eventName, handler, eventClass)); return this; @@ -61,7 +62,8 @@ public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler hand return this; } - public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, Class eventClass) { + public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, + Class eventClass) { eventNotificationListener.add(new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } @@ -71,7 +73,8 @@ public HandlerRegistry listenNotificationCloudEvent(String eventName, CloudEvent return this; } - public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, Class eventClass) { + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, + Class eventClass) { dynamicEventHandlers.add(new RegisteredEventListener<>(eventNamePattern, handler, eventClass)); return this; } @@ -92,7 +95,9 @@ public HandlerRegistry handleCloudEventCommand(String commandName, CloudCommandH } public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) { - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass)); + handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> + handler.handle(message), queryClass + )); return this; } @@ -102,7 +107,9 @@ public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate HandlerRegistry serveCloudEventQuery(String resource, QueryHandler handler) { - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), CloudEvent.class)); + handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> + handler.handle(message), CloudEvent.class + )); return this; } diff --git a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java index 5cda7121..af3db020 100644 --- a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java +++ b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java @@ -34,7 +34,9 @@ void shouldListenDomainEvent() { assertThat(registry.getDomainEventListeners().get(domain)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); } @@ -46,7 +48,9 @@ void shouldListenDomainCloudEvent() { assertThat(registry.getDomainEventListeners().get(domain)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); } @@ -58,7 +62,9 @@ void shouldListenEvent() { assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); } @@ -70,7 +76,9 @@ void shouldListenCloudEvent() { assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); } @@ -109,13 +117,15 @@ void shouldRegisterNotificationCloudEventListener() { @Test @SuppressWarnings("unchecked") - public void listenEvent() { + void listenEvent() { SomeDomainEventHandler handler = mock(SomeDomainEventHandler.class); registry.listenEvent(name, handler, SomeDataClass.class); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, SomeDataClass.class, handler)).hasSize(1); } @@ -127,7 +137,9 @@ void shouldListenDynamicEvent() { assertThat(registry.getDynamicEventHandlers()) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); } @@ -139,7 +151,9 @@ void shouldListenDynamicCloudEvent() { assertThat(registry.getDynamicEventHandlers()) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); } @@ -151,7 +165,9 @@ void handleDomainCommand() { assertThat(registry.getCommandHandlers()) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, RegisteredCommandHandler::getHandler) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, + RegisteredCommandHandler::getHandler + ) .containsExactly(name, SomeDataClass.class, handler)).hasSize(1); } @@ -163,7 +179,9 @@ void handleCloudEventCommand() { assertThat(registry.getCommandHandlers()) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, RegisteredCommandHandler::getHandler) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, + RegisteredCommandHandler::getHandler + ) .containsExactly(name, CloudEvent.class, cloudCommandHandler)).hasSize(1); } @@ -206,7 +224,8 @@ void serveQueryWithTypeInference() { assertThat(registry.getHandlers()).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); - assertThat(registered).extracting(RegisteredQueryHandler::getHandler).isInstanceOf(QueryHandlerDelegate.class); + assertThat(registered).extracting(RegisteredQueryHandler::getHandler) + .isInstanceOf(QueryHandlerDelegate.class); }).hasSize(1); } @@ -278,7 +297,6 @@ public Mono handle(CloudEvent message) { } } - @Data private static class SomeDataClass { private String someProp1; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java index 4ceb6641..b0e36641 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java @@ -1,13 +1,13 @@ package org.reactivecommons.async.commons; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.commons.communications.Message; import reactor.core.publisher.Mono; import java.util.function.Function; -@AllArgsConstructor +@RequiredArgsConstructor public class CommandExecutor { private final CommandHandler eventHandler; private final Function converter; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java index 78510323..4c5e59fe 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.databind.JsonNode; import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; -import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; @@ -19,7 +19,7 @@ import static org.reactivecommons.async.commons.converters.json.JacksonMessageConverter.APPLICATION_CLOUD_EVENT_JSON; @Log -@AllArgsConstructor +@RequiredArgsConstructor public class DLQDiscardNotifier implements DiscardNotifier { private final DomainEventBus eventBus; private final MessageConverter messageConverter; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java index fef94e99..39657bf6 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java @@ -1,13 +1,13 @@ package org.reactivecommons.async.commons; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.commons.communications.Message; import reactor.core.publisher.Mono; import java.util.function.Function; -@AllArgsConstructor +@RequiredArgsConstructor public class EventExecutor { private final EventHandler eventHandler; private final Function converter; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java index 3648f67e..d30525fa 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java @@ -1,12 +1,12 @@ package org.reactivecommons.async.commons; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor public enum FallbackStrategy { FAST_RETRY("ATTENTION!! Fast retry message to same Queue: %s"), DEFINITIVE_DISCARD("ATTENTION!! DEFINITIVE DISCARD!! of the message: %s"), RETRY_DLQ("ATTENTION!! Sending message to Retry DLQ: %s"); public final String message; - FallbackStrategy(String message){ - this.message = message; - } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java index 17bbe0c4..562461ac 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java @@ -80,10 +80,7 @@ public void addQueryHandler(RegisteredQueryHandler handler) { } private Function getMatchHandler(Map handlers) { - return name -> { - String matched = matcher.match(handlers.keySet(), name); - return handlers.get(matched); - }; + return name -> handlers.get(matcher.match(handlers.keySet(), name)); } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java index 62ec7479..cceb6625 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java @@ -16,33 +16,35 @@ import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -@NoArgsConstructor(access = AccessLevel.PRIVATE) @Log -public class HandlerResolverBuilder { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class HandlerResolverBuilder { - public static HandlerResolver buildResolver(String domain, - Map registries, + public static HandlerResolver buildResolver(String domain, Map registries, final DefaultCommandHandler defaultCommandHandler) { if (DEFAULT_DOMAIN.equals(domain)) { final ConcurrentMap> queryHandlers = registries .values().stream() .flatMap(r -> r.getHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); final ConcurrentMap> commandHandlers = registries .values().stream() .flatMap(r -> r.getCommandHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); final ConcurrentMap> eventNotificationListener = registries .values() .stream() .flatMap(r -> r.getEventNotificationListener().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); @@ -79,9 +81,8 @@ public RegisteredCommandHandler getCommandHandler(String path) { }; } - private static ConcurrentMap> getEventHandlersWithDynamics(String domain, - Map registries) { + private static ConcurrentMap> getEventHandlersWithDynamics( + String domain, Map registries) { // event handlers and dynamic handlers return registries .values().stream() @@ -91,8 +92,9 @@ public RegisteredCommandHandler getCommandHandler(String path) { } return Stream.empty(); }) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); } private static Stream> getDynamics(String domain, HandlerRegistry r) { @@ -102,8 +104,8 @@ public RegisteredCommandHandler getCommandHandler(String path) { return Stream.of(); } - private static ConcurrentMap> getEventsToBind(String domain, Map registries) { + private static ConcurrentMap> getEventsToBind( + String domain, Map registries) { return registries .values().stream() .flatMap(r -> { @@ -112,7 +114,8 @@ public RegisteredCommandHandler getCommandHandler(String path) { } return Stream.empty(); }) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java index 05bbe553..4058b4ba 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java @@ -1,5 +1,10 @@ package org.reactivecommons.async.commons; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class Headers { public static final String REPLY_ID = "x-reply_id"; @@ -8,9 +13,4 @@ public final class Headers { public static final String SERVED_QUERY_ID = "x-serveQuery-id"; public static final String SOURCE_APPLICATION = "sourceApplication"; public static final String REPLY_TIMEOUT_MILLIS = "x-reply-timeout-millis"; - - - private Headers() { - } - } 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 e3bd99f9..9ea2fbe6 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,13 +1,15 @@ package org.reactivecommons.async.commons.config; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.time.Duration; import java.util.UUID; @Getter +@RequiredArgsConstructor public class BrokerConfig { - private final String routingKey = UUID.randomUUID().toString().replaceAll("-", ""); + private final String routingKey = UUID.randomUUID().toString().replace("-", ""); private final boolean persistentQueries; private final boolean persistentCommands; private final boolean persistentEvents; @@ -20,11 +22,4 @@ public BrokerConfig() { this.replyTimeout = Duration.ofSeconds(15); } - public BrokerConfig(boolean persistentQueries, boolean persistentCommands, boolean persistentEvents, Duration replyTimeout) { - this.persistentQueries = persistentQueries; - this.persistentCommands = persistentCommands; - this.persistentEvents = persistentEvents; - this.replyTimeout = replyTimeout; - } - } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java index 8ceb7b0d..311f5137 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java @@ -3,13 +3,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.SneakyThrows; -import lombok.experimental.UtilityClass; import java.util.Objects; -@UtilityClass -public class CloudEventBuilderExt { +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class CloudEventBuilderExt { private static final ObjectMapper mapper = new ObjectMapper(); @SneakyThrows diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java index 2a94f7aa..ef4bbbea 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java @@ -13,23 +13,27 @@ public interface CustomReporter { String QUERY_CLASS = "org.reactivecommons.async.api.AsyncQuery"; default Mono reportError(Throwable ex, Message rawMessage, Object message, boolean redelivered) { - switch (message.getClass().getName()){ - case COMMAND_CLASS: - return reportError(ex, rawMessage, (Command) message, redelivered); - case EVENT_CLASS: - return reportError(ex, rawMessage, (DomainEvent) message, redelivered); - case QUERY_CLASS: - return reportError(ex, rawMessage, (AsyncQuery) message, redelivered); - default: - return Mono.empty(); - } + var name = message.getClass().getName(); + return Mono.just(name) + .filter(COMMAND_CLASS::equals) + .flatMap(n -> reportError(ex, rawMessage, (Command) message, redelivered)) + .switchIfEmpty(Mono.just(name) + .filter(EVENT_CLASS::equals) + .flatMap(n -> reportError(ex, rawMessage, (DomainEvent) message, redelivered)) + .switchIfEmpty(Mono.just(name) + .filter(QUERY_CLASS::equals) + .flatMap(n -> reportError(ex, rawMessage, (AsyncQuery) message, redelivered)) + ) + ); } default void reportMetric(String type, String handlerPath, Long duration, boolean success) { } Mono reportError(Throwable ex, Message rawMessage, Command message, boolean redelivered); + Mono reportError(Throwable ex, Message rawMessage, DomainEvent message, boolean redelivered); + Mono reportError(Throwable ex, Message rawMessage, AsyncQuery message, boolean redelivered); } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java index 009678f4..0a67edec 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java @@ -1,11 +1,14 @@ package org.reactivecommons.async.commons.utils; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + import java.util.ArrayList; import java.util.Arrays; -public class ArrayUtils { - private ArrayUtils(){} +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class ArrayUtils { public static Object[] prefixArray(E head, E[] tail) { final ArrayList objects = new ArrayList<>(1 + tail.length); diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java index 0d2aff9c..26168a9e 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java @@ -1,5 +1,6 @@ package org.reactivecommons.async.commons.utils; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.SignalType; @@ -10,6 +11,7 @@ @Log +@RequiredArgsConstructor public class LoggerSubscriber extends BaseSubscriber { private final String flowName; @@ -18,10 +20,6 @@ public class LoggerSubscriber extends BaseSubscriber { private static final String ON_CANCEL_MSG = "%s: ##On Cancel Hook!!"; private static final String ON_FINALLY_MSG = "%s: ##On Finally Hook! Signal type: %s"; - public LoggerSubscriber(String flowName) { - this.flowName = flowName; - } - @Override protected void hookOnComplete() { log.warning(format(ON_COMPLETE_MSG)); diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java index 40013ec8..259c3d25 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java @@ -1,10 +1,14 @@ package org.reactivecommons.async.commons.utils; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + import java.nio.ByteBuffer; import java.util.Base64; import java.util.UUID; -public class NameGenerator { +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class NameGenerator { public static String fromNameWithSuffix(String appName, String suffix) { if (suffix != null && !suffix.isEmpty()) { @@ -27,7 +31,7 @@ private static String generateName(String applicationName, String suffix) { bb.putLong(uuid.getMostSignificantBits()) .putLong(uuid.getLeastSignificantBits()); // Convert to base64 and remove trailing = - String realSuffix = suffix != null && !"".equals(suffix) ? suffix + "." : ""; + String realSuffix = suffix != null && !suffix.isEmpty() ? suffix + "." : ""; return applicationName + "." + realSuffix + encodeToUrlSafeString(bb.array()) .replace("=", ""); } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java index 47e0e11b..39a4e876 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java @@ -1,6 +1,7 @@ package org.reactivecommons.async.commons.utils.resolver; -import lombok.experimental.UtilityClass; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; @@ -15,8 +16,8 @@ import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -@UtilityClass -public class HandlerResolverUtil { +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class HandlerResolverUtil { public static HandlerResolver fromHandlerRegistries(Collection registries, CommandHandler defaultHandler) { diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java index 4878fcb1..c43d8ab7 100644 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java @@ -14,7 +14,7 @@ class ReactiveReplyRouterTest { private final ReactiveReplyRouter replyRouter = new ReactiveReplyRouter(); @Test - void shouldRouteReply(){ + void shouldRouteReply() { final String uuid = UUID.randomUUID().toString(); final Mono registered = replyRouter.register(uuid); @@ -28,18 +28,18 @@ void shouldRouteReply(){ } @Test - void shouldRouteEmptyResponse(){ + void shouldRouteEmptyResponse() { final String uuid = UUID.randomUUID().toString(); final Mono registered = replyRouter.register(uuid); replyRouter.routeEmpty(uuid); StepVerifier.create(registered) - .verifyComplete(); + .verifyComplete(); } @Test - void shouldDeRegisterProcessor(){ + void shouldDeRegisterProcessor() { final String uuid = UUID.randomUUID().toString(); final Mono registered = replyRouter.register(uuid); @@ -47,7 +47,7 @@ void shouldDeRegisterProcessor(){ replyRouter.routeEmpty(uuid); StepVerifier.create(registered.timeout(Duration.ofSeconds(1))) - .expectTimeout(Duration.ofSeconds(3)).verify(); + .expectTimeout(Duration.ofSeconds(3)).verify(); } } diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java index c07c9ea9..d1e7870c 100755 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -21,7 +22,7 @@ class KeyMatcherPerformanceManualTest { Map candidates = new HashMap<>(); - private KeyMatcher keyMatcher = new KeyMatcher(); + private final KeyMatcher keyMatcher = new KeyMatcher(); private List testList; private List testResultList; @@ -29,7 +30,7 @@ class KeyMatcherPerformanceManualTest { @BeforeEach public void init() { ClassLoader classLoader = getClass().getClassLoader(); - File file = new File(classLoader.getResource("candidateNamesForMatching.txt").getFile()); + File file = new File(Objects.requireNonNull(classLoader.getResource("candidateNamesForMatching.txt")).getFile()); try { Set names = new HashSet<>(Files .readAllLines(Paths.get(file.getAbsolutePath()))); diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java index b607233d..eec4cb12 100755 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -21,7 +22,7 @@ class KeyMatcherPerformanceWildcardTest { Map candidates = new HashMap<>(); - private KeyMatcher keyMatcher = new KeyMatcher(); + private final KeyMatcher keyMatcher = new KeyMatcher(); private List testList; private List testResultList; @@ -29,8 +30,12 @@ class KeyMatcherPerformanceWildcardTest { @BeforeEach public void setUp() { ClassLoader classLoader = getClass().getClassLoader(); - File file = new File(classLoader.getResource("wildcard_names_for_matching.txt").getFile()); - File file2 = new File(classLoader.getResource("concrete_names_for_matching.txt").getFile()); + var file = new File(Objects.requireNonNull( + classLoader.getResource("wildcard_names_for_matching.txt")).getFile() + ); + var file2 = new File( + Objects.requireNonNull(classLoader.getResource("concrete_names_for_matching.txt")).getFile() + ); try { Set names = new HashSet<>(Files .readAllLines(Paths.get(file.getAbsolutePath()))); 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 cbd5a09b..b31f4a78 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 @@ -1,13 +1,13 @@ package org.reactivecommons.async.kafka; import io.cloudevents.CloudEvent; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; import org.reactivestreams.Publisher; -@AllArgsConstructor +@RequiredArgsConstructor public class KafkaDomainEventBus implements DomainEventBus { public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; private final ReactiveMessageSender sender; diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java index 7c5092de..bafda174 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java @@ -29,18 +29,18 @@ public String getContentType() { } } - public static KafkaMessage fromDelivery(ReceiverRecord record) { - return new KafkaMessage(record.value(), createMessageProps(record)); + public static KafkaMessage fromDelivery(ReceiverRecord receiverRecord) { + return new KafkaMessage(receiverRecord.value(), createMessageProps(receiverRecord)); } - private static Properties createMessageProps(ReceiverRecord record) { - Map headers = parseHeaders(record.headers()); + private static Properties createMessageProps(ReceiverRecord receiverRecord) { + Map headers = parseHeaders(receiverRecord.headers()); final KafkaMessageProperties properties = new KafkaMessageProperties(); properties.setHeaders(headers); - properties.setKey(record.key()); - properties.setTopic(record.topic()); - properties.setContentLength(record.value().length); + properties.setKey(receiverRecord.key()); + properties.setTopic(receiverRecord.topic()); + properties.setContentLength(receiverRecord.value().length); return properties; } diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java index 9cdf1921..e46f4b85 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java @@ -1,6 +1,6 @@ package org.reactivecommons.async.kafka.communications; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import reactor.core.publisher.Flux; import reactor.kafka.receiver.KafkaReceiver; import reactor.kafka.receiver.ReceiverOptions; @@ -13,7 +13,7 @@ import static org.apache.kafka.clients.consumer.ConsumerConfig.MAX_POLL_RECORDS_CONFIG; -@AllArgsConstructor +@RequiredArgsConstructor public class ReactiveMessageListener { private final ReceiverOptions receiverOptions; diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java index 7e13d05a..99d97608 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java @@ -26,13 +26,18 @@ public class ReactiveMessageSender { private final ConcurrentHashMap> confirmations = new ConcurrentHashMap<>(); - private final CopyOnWriteArrayList>> fluxSinks = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList>> fluxSinks = + new CopyOnWriteArrayList<>(); private final AtomicLong counter = new AtomicLong(); - private final ExecutorService executorServiceConfirm = Executors.newFixedThreadPool(13, r -> new Thread(r, "KMessageSender1-" + counter.getAndIncrement())); - private final ExecutorService executorServiceEmit = Executors.newFixedThreadPool(13, r -> new Thread(r, "KMessageSender2-" + counter.getAndIncrement())); + private final ExecutorService executorServiceConfirm = Executors.newFixedThreadPool(13, r -> + new Thread(r, "KMessageSender1-" + counter.getAndIncrement()) + ); + private final ExecutorService executorServiceEmit = Executors.newFixedThreadPool(13, r -> + new Thread(r, "KMessageSender2-" + counter.getAndIncrement()) + ); - private final int senderCount = 4; + private static final int SENDER_COUNT = 4; private final MessageConverter messageConverter; private final TopologyCreator topologyCreator; @@ -41,7 +46,7 @@ public ReactiveMessageSender(KafkaSender sender, MessageConverte TopologyCreator topologyCreator) { this.messageConverter = messageConverter; this.topologyCreator = topologyCreator; - for (int i = 0; i < senderCount; ++i) { + for (int i = 0; i < SENDER_COUNT; ++i) { Flux> source = Flux.create(fluxSinks::add); sender.send(source) .doOnNext(this::confirm) @@ -51,9 +56,10 @@ public ReactiveMessageSender(KafkaSender sender, MessageConverte public Mono send(V message) { return Mono.create(sink -> { - SenderRecord record = createRecord(message); - confirmations.put(record.key(), sink); - executorServiceEmit.submit(() -> fluxSinks.get((int) (System.currentTimeMillis() % senderCount)).next(record)); + SenderRecord senderRecord = createRecord(message); + confirmations.put(senderRecord.key(), sink); + executorServiceEmit.submit(() -> fluxSinks.get((int) (System.currentTimeMillis() % SENDER_COUNT)) + .next(senderRecord)); }); } @@ -72,8 +78,8 @@ private void confirm(SenderResult result) { private SenderRecord createRecord(V object) { KafkaMessage message = (KafkaMessage) messageConverter.toMessage(object); - ProducerRecord record = createProducerRecord(message); - return SenderRecord.create(record, message.getProperties().getKey()); // TODO: Review for Request-Reply + ProducerRecord producerRecord = createProducerRecord(message); + return SenderRecord.create(producerRecord, message.getProperties().getKey()); // TODO: Review for Request-Reply } @SneakyThrows diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java index f7217197..7b5e4a8c 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java @@ -38,16 +38,15 @@ public Map getTopics() { } public Mono createTopics(List topics) { - TopicCustomization.TopicCustomizationBuilder defaultBuilder = TopicCustomization.builder() - .partitions(-1) - .replicationFactor((short) -1); - return Flux.fromIterable(topics) .map(topic -> { if (customizations.getTopics().containsKey(topic)) { return customizations.getTopics().get(topic); } - return defaultBuilder.topic(topic).build(); + return TopicCustomization.builder() + .partitions(-1) + .replicationFactor((short) -1) + .topic(topic).build(); }) .map(this::toNewTopic) .flatMap(this::createTopic) diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java index da47e237..fc20596a 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java @@ -28,7 +28,6 @@ @Log public abstract class GenericMessageListener { - public static final int DEFAULT_RETRIES = 10; private final ConcurrentHashMap>> handlers = new ConcurrentHashMap<>(); private final ReactiveMessageListener messageListener; private final Scheduler scheduler = Schedulers.newParallel(getClass().getSimpleName(), 12); @@ -46,9 +45,10 @@ public abstract class GenericMessageListener { private final CustomReporter customReporter; private volatile Flux> messageFlux; - public GenericMessageListener(ReactiveMessageListener listener, boolean useDLQ, boolean createTopology, - long maxRetries, long retryDelay, DiscardNotifier discardNotifier, - String objectType, CustomReporter customReporter, String groupId, List topics) { + protected GenericMessageListener(ReactiveMessageListener listener, boolean useDLQ, boolean createTopology, + long maxRetries, long retryDelay, DiscardNotifier discardNotifier, + String objectType, CustomReporter customReporter, String groupId, + List topics) { this.groupId = groupId; this.topics = topics; this.messageListener = listener; @@ -71,8 +71,8 @@ protected Mono setUpBindings(TopologyCreator creator) { public void startListener(TopologyCreator creator) { log.log(Level.INFO, "Using max concurrency {0}, in receiver for topics: {1}", - new Object[]{messageListener.getMaxConcurrency(), topics}); + if (useDLQ) { log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", new Object[]{maxRetries}); @@ -103,7 +103,7 @@ private Flux> consumeFaultTolerant(Flux { final Instant init = Instant.now(); return handle(msj, init) - .doOnSuccess(record -> record.receiverOffset().acknowledge()) + .doOnSuccess(r -> r.receiverOffset().acknowledge()) .onErrorResume(err -> requeueOrAck(msj, err, init)); }, messageListener.getMaxConcurrency()); } @@ -187,7 +187,9 @@ private void doLogExecution(String executorPath, long timeElapsed) { protected void logError(Throwable err, ReceiverRecord msj, FallbackStrategy strategy) { String messageID = msj.key(); try { - log.log(Level.SEVERE, format("Error encounter while processing message %s: %s", messageID, err.toString()), err); + log.log(Level.SEVERE, + format("Error encounter while processing message %s: %s", messageID, err.toString()), err + ); log.warning(format("Message %s Headers: %s", messageID, msj.headers().toString())); log.warning(format("Message %s Body: %s", messageID, new String(msj.value()))); } catch (Exception e) { 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 index d1229f58..9bdcc3ef 100644 --- 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 @@ -29,20 +29,42 @@ class KafkaDirectAsyncGatewayTest { @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.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/KafkaMessageTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java index 4ac1dec5..3433ebb4 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java @@ -16,19 +16,19 @@ class KafkaMessageTest { @Mock - private ReceiverRecord record; + private ReceiverRecord receiverRecord; @Test void shouldParse() { // Arrange RecordHeaders headers = new RecordHeaders(); headers.add("content-type", "application/json".getBytes()); - when(record.value()).thenReturn("value".getBytes()); - when(record.key()).thenReturn("key"); - when(record.topic()).thenReturn("topic"); - when(record.headers()).thenReturn(headers); + when(receiverRecord.value()).thenReturn("value".getBytes()); + when(receiverRecord.key()).thenReturn("key"); + when(receiverRecord.topic()).thenReturn("topic"); + when(receiverRecord.headers()).thenReturn(headers); // Act - Message message = KafkaMessage.fromDelivery(record); + Message message = KafkaMessage.fromDelivery(receiverRecord); // Assert assertEquals("key", message.getProperties().getKey()); assertEquals("topic", message.getProperties().getTopic()); @@ -41,12 +41,12 @@ void shouldParse() { void shouldParseWhenNoContentType() { // Arrange RecordHeaders headers = new RecordHeaders(); - when(record.value()).thenReturn("value".getBytes()); - when(record.key()).thenReturn("key"); - when(record.topic()).thenReturn("topic"); - when(record.headers()).thenReturn(headers); + when(receiverRecord.value()).thenReturn("value".getBytes()); + when(receiverRecord.key()).thenReturn("key"); + when(receiverRecord.topic()).thenReturn("topic"); + when(receiverRecord.headers()).thenReturn(headers); // Act - Message message = KafkaMessage.fromDelivery(record); + Message message = KafkaMessage.fromDelivery(receiverRecord); // Assert assertEquals("key", message.getProperties().getKey()); assertEquals("topic", message.getProperties().getTopic()); diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java index 2f8dcb41..83b3f30b 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java @@ -9,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class KafkaCustomizationsTest { +class KafkaCustomizationsTest { private KafkaCustomizations customizations; diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java index 83dbdeb9..014a7aca 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java @@ -30,7 +30,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -@SuppressWarnings({"rawtypes", "unchecked"}) +@SuppressWarnings("unchecked") @ExtendWith(MockitoExtension.class) class GenericMessageListenerTest { @@ -64,14 +64,14 @@ SampleListener setup(Function> handler) { @Test void shouldStartListener() { // Arrange - ReceiverRecord record = mock(ReceiverRecord.class); - when(record.topic()).thenReturn("topic"); - when(record.value()).thenReturn("message".getBytes(StandardCharsets.UTF_8)); + ReceiverRecord receiverRecord = mock(ReceiverRecord.class); + when(receiverRecord.topic()).thenReturn("topic"); + when(receiverRecord.value()).thenReturn("message".getBytes(StandardCharsets.UTF_8)); Headers header = new RecordHeaders().add("contentType", "application/json".getBytes(StandardCharsets.UTF_8)); - when(record.headers()).thenReturn(header); - when(record.key()).thenReturn("key"); + when(receiverRecord.headers()).thenReturn(header); + when(receiverRecord.key()).thenReturn("key"); - Flux> flux = Flux.just(record); + Flux> flux = Flux.just(receiverRecord); when(receiver.listen(anyString(), any(List.class))).thenReturn(flux); when(receiver.getMaxConcurrency()).thenReturn(1); when(topologyCreator.createTopics(any(List.class))).thenReturn(Mono.empty()); @@ -89,7 +89,6 @@ void shouldStartListener() { verify(topologyCreator, times(1)).createTopics(any(List.class)); } - public static class SampleListener extends GenericMessageListener { private final Function> handler; diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java index 8e22c31a..fcca072a 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java @@ -32,8 +32,9 @@ public Mono listenEvent(String eventName, DomainEventHandler fn, Cl @Override public void serveQuery(String resource, QueryHandler handler, Class queryClass) { - resolver.addQueryHandler(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message) - , queryClass)); + resolver.addQueryHandler(new RegisteredQueryHandler<>(resource, + (ignored, message) -> handler.handle(message), queryClass) + ); } @Override diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java index eb35f00c..3ae9856a 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java @@ -1,6 +1,7 @@ package org.reactivecommons.async.rabbit.communications; import com.rabbitmq.client.AMQP; +import lombok.Getter; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.exceptions.SendFailureNoAckException; @@ -26,58 +27,72 @@ import static org.reactivecommons.async.commons.Headers.SOURCE_APPLICATION; public class ReactiveMessageSender { + @Getter private final Sender sender; private final String sourceApplication; private final MessageConverter messageConverter; + @Getter private final TopologyCreator topologyCreator; - private final int numberOfSenderSubscriptions = 4; + private static final int NUMBER_OF_SENDER_SUBSCRIPTIONS = 4; private final CopyOnWriteArrayList> fluxSinkConfirm = new CopyOnWriteArrayList<>(); private volatile FluxSink fluxSinkNoConfirm; private final AtomicLong counter = new AtomicLong(); - private final ExecutorService executorService = Executors.newFixedThreadPool(13, r -> new Thread(r, "RMessageSender1-" + counter.getAndIncrement())); - private final ExecutorService executorService2 = Executors.newFixedThreadPool(13, r -> new Thread(r, "RMessageSender2-" + counter.getAndIncrement())); + private final ExecutorService executorService = Executors.newFixedThreadPool( + 13, r -> new Thread(r, "RMessageSender1-" + counter.getAndIncrement()) + ); + private final ExecutorService executorService2 = Executors.newFixedThreadPool( + 13, r -> new Thread(r, "RMessageSender2-" + counter.getAndIncrement()) + ); - public ReactiveMessageSender(Sender sender, String sourceApplication, MessageConverter messageConverter, TopologyCreator topologyCreator) { + public ReactiveMessageSender(Sender sender, String sourceApplication, + MessageConverter messageConverter, TopologyCreator topologyCreator) { this.sender = sender; this.sourceApplication = sourceApplication; this.messageConverter = messageConverter; this.topologyCreator = topologyCreator; - for (int i = 0; i < numberOfSenderSubscriptions; ++i) { + for (int i = 0; i < NUMBER_OF_SENDER_SUBSCRIPTIONS; ++i) { final Flux messageSource = Flux.create(fluxSinkConfirm::add); sender.sendWithTypedPublishConfirms(messageSource) .doOnNext((OutboundMessageResult outboundMessageResult) -> { - final Consumer ackNotifier = outboundMessageResult.getOutboundMessage().getAckNotifier(); - executorService.submit(() -> ackNotifier.accept(outboundMessageResult.isAck())); - }).subscribe(); + final Consumer ackNotifier = outboundMessageResult.getOutboundMessage().getAckNotifier(); + executorService.submit(() -> ackNotifier.accept(outboundMessageResult.isAck())); + }).subscribe(); } - final Flux messageSourceNoConfirm = Flux.create(fluxSink -> { - this.fluxSinkNoConfirm = fluxSink; - }); + final Flux messageSourceNoConfirm = Flux.create(fluxSink -> + this.fluxSinkNoConfirm = fluxSink + ); sender.send(messageSourceNoConfirm).subscribe(); } - public Mono sendWithConfirm(T message, String exchange, String routingKey, Map headers, boolean persistent) { + public Mono sendWithConfirm(T message, String exchange, String routingKey, + Map headers, boolean persistent) { return Mono.create(monoSink -> { Consumer notifier = new AckNotifier(monoSink); - final MyOutboundMessage outboundMessage = toOutboundMessage(message, exchange, routingKey, headers, notifier, persistent); - executorService2.submit(() -> fluxSinkConfirm.get((int) (System.currentTimeMillis() % numberOfSenderSubscriptions)).next(outboundMessage)); + final MyOutboundMessage outboundMessage = toOutboundMessage( + message, exchange, routingKey, headers, notifier, persistent + ); + executorService2.submit(() -> fluxSinkConfirm.get( + (int) (System.currentTimeMillis() % NUMBER_OF_SENDER_SUBSCRIPTIONS)).next(outboundMessage) + ); }); } - public Mono sendNoConfirm(T message, String exchange, String routingKey, Map headers, boolean persistent) { + public Mono sendNoConfirm(T message, String exchange, String routingKey, + Map headers, boolean persistent) { fluxSinkNoConfirm.next(toOutboundMessage(message, exchange, routingKey, headers, persistent)); return Mono.empty(); } - public Flux sendWithConfirmBatch(Flux messages, String exchange, String routingKey, Map headers, boolean persistent) { + public Flux sendWithConfirmBatch(Flux messages, String exchange, String routingKey, + Map headers, boolean persistent) { return messages.map(message -> toOutboundMessage(message, exchange, routingKey, headers, persistent)) .as(sender::sendWithPublishConfirms) .flatMap(result -> result.isAck() ? @@ -95,7 +110,7 @@ public AckNotifier(MonoSink monoSink) { @Override public void accept(Boolean ack) { - if (ack) { + if (Boolean.TRUE.equals(ack)) { monoSink.success(); } else { monoSink.error(new SendFailureNoAckException("No ACK when sending message")); @@ -104,33 +119,37 @@ public void accept(Boolean ack) { } + @Getter static class MyOutboundMessage extends OutboundMessage { private final Consumer ackNotifier; - public MyOutboundMessage(String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body, Consumer ackNotifier) { + public MyOutboundMessage( + String exchange, String routingKey, AMQP.BasicProperties properties, + byte[] body, Consumer ackNotifier + ) { super(exchange, routingKey, properties, body); this.ackNotifier = ackNotifier; } - - public Consumer getAckNotifier() { - return ackNotifier; - } } - private MyOutboundMessage toOutboundMessage(T object, String exchange, String routingKey, Map headers, Consumer ackNotifier, boolean persistent) { + private MyOutboundMessage toOutboundMessage(T object, String exchange, + String routingKey, Map headers, + Consumer ackNotifier, boolean persistent) { final Message message = messageConverter.toMessage(object); final AMQP.BasicProperties props = buildMessageProperties(message, headers, persistent); return new MyOutboundMessage(exchange, routingKey, props, message.getBody(), ackNotifier); } - private OutboundMessage toOutboundMessage(T object, String exchange, String routingKey, Map headers, boolean persistent) { + private OutboundMessage toOutboundMessage(T object, String exchange, String routingKey, + Map headers, boolean persistent) { final Message message = messageConverter.toMessage(object); final AMQP.BasicProperties props = buildMessageProperties(message, headers, persistent); return new OutboundMessage(exchange, routingKey, props, message.getBody()); } - private AMQP.BasicProperties buildMessageProperties(Message message, Map headers, boolean persistent) { + private AMQP.BasicProperties buildMessageProperties(Message message, Map headers, + boolean persistent) { final Message.Properties properties = message.getProperties(); final Map baseHeaders = new HashMap<>(properties.getHeaders()); baseHeaders.putAll(headers); @@ -148,13 +167,5 @@ private AMQP.BasicProperties buildMessageProperties(Message message, Map unbind(BindingSpecification binding) { .onErrorMap(TopologyDefException::new); } - public Mono declareDLQ(String originQueue, String retryTarget, int retryTime, Optional maxLengthBytesOpt) { + public Mono declareDLQ(String originQueue, String retryTarget, int retryTime, + Optional maxLengthBytesOpt) { final Map args = new HashMap<>(); args.put("x-dead-letter-exchange", retryTarget); args.put("x-message-ttl", retryTime); @@ -55,7 +56,8 @@ public Mono declareDLQ(String originQueue, String retryTar return declare(specification); } - public Mono declareQueue(String name, String dlqExchange, Optional maxLengthBytesOpt) { + public Mono declareQueue(String name, String dlqExchange, + Optional maxLengthBytesOpt) { return declareQueue(name, dlqExchange, maxLengthBytesOpt, Optional.empty()); } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java index 2e04b22f..42316813 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java @@ -64,13 +64,25 @@ public ApplicationCommandListener(ReactiveMessageListener listener, } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(directExchange).durable(true).type("direct")); + final Mono declareExchange = creator.declare( + ExchangeSpecification.exchange(directExchange).durable(true).type("direct") + ); if (withDLQRetry) { - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(directExchange + DQL).durable(true).type("direct")); - final Mono declareQueue = creator.declareQueue(queueName, directExchange + DQL, maxLengthBytes); - final Mono declareDLQ = creator.declareDLQ(queueName, directExchange, retryDelay, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(directExchange + DQL, queueName, queueName + DQL)); + final Mono declareExchangeDLQ = creator.declare( + ExchangeSpecification.exchange(directExchange + DQL).durable(true).type("direct") + ); + final Mono declareQueue = creator.declareQueue( + queueName, directExchange + DQL, maxLengthBytes + ); + final Mono declareDLQ = creator.declareDLQ( + queueName, directExchange, retryDelay, maxLengthBytes + ); + final Mono binding = creator.bind(BindingSpecification.binding( + directExchange, queueName, queueName) + ); + final Mono bindingDLQ = creator.bind( + BindingSpecification.binding(directExchange + DQL, queueName, queueName + DQL) + ); return declareExchange.then(declareExchangeDLQ) .then(declareDLQ) .then(declareQueue) @@ -80,7 +92,9 @@ protected Mono setUpBindings(TopologyCreator creator) { .then(); } else { final Mono declareQueue = creator.declareQueue(queueName, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, queueName, queueName) + ); return declareExchange.then(declareQueue).then(binding).then(declareDelayedTopology(creator)).then(); } } @@ -88,8 +102,12 @@ protected Mono setUpBindings(TopologyCreator creator) { private Mono declareDelayedTopology(TopologyCreator creator) { if (delayedCommands) { String delayedQueue = queueName + "-delayed"; - final Mono declareQueue = creator.declareQueue(delayedQueue, directExchange, maxLengthBytes, Optional.of(queueName)); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, delayedQueue, delayedQueue)); + final Mono declareQueue = creator.declareQueue( + delayedQueue, directExchange, maxLengthBytes, Optional.of(queueName) + ); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, delayedQueue, delayedQueue) + ); return declareQueue.then(binding).then(); } return Mono.empty(); @@ -109,9 +127,8 @@ protected String getExecutorPath(AcknowledgableDelivery msj) { JsonNode jsonNode = messageConverter.readValue(rabbitMessage, JsonNode.class); if (jsonNode.get(COMMAND_ID) != null) { return jsonNode.get(NAME).asText(); - } else { - return jsonNode.get(TYPE).asText(); } + return jsonNode.get(TYPE).asText(); } @Override diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java index 73f1e481..11cfd5ba 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java @@ -61,18 +61,42 @@ public ApplicationEventListener(ReactiveMessageListener receiver, } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(eventsExchange).durable(true).type("topic")); - final Flux bindings = fromIterable(resolver.getEventListeners()).flatMap(listener -> creator.bind(BindingSpecification.binding(eventsExchange, listener.getPath(), queueName))); + final Mono declareExchange = creator.declare( + ExchangeSpecification.exchange(eventsExchange).durable(true).type("topic") + ); + final Flux bindings = fromIterable(resolver.getEventListeners()) + .flatMap(listener -> creator.bind( + BindingSpecification.binding(eventsExchange, listener.getPath(), queueName) + )); if (withDLQRetry) { final String eventsDLQExchangeName = format("%s.%s.DLQ", appName, eventsExchange); final String retryExchangeName = format("%s.%s", appName, eventsExchange); - final Mono retryExchange = creator.declare(ExchangeSpecification.exchange(retryExchangeName).durable(true).type("topic")); - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(eventsDLQExchangeName).durable(true).type("topic")); - final Mono declareDLQ = creator.declareDLQ(queueName, retryExchangeName, retryDelay, maxLengthBytes); - final Mono declareQueue = creator.declareQueue(queueName, eventsDLQExchangeName, maxLengthBytes); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(eventsDLQExchangeName, "#", queueName + ".DLQ")); - final Mono retryBinding = creator.bind(BindingSpecification.binding(retryExchangeName, "#", queueName)); - return declareExchange.then(retryExchange).then(declareExchangeDLQ).then(declareQueue).then(declareDLQ).thenMany(bindings).then(bindingDLQ).then(retryBinding).then(); + final Mono retryExchange = creator.declare( + ExchangeSpecification.exchange(retryExchangeName).durable(true).type("topic") + ); + final Mono declareExchangeDLQ = creator.declare( + ExchangeSpecification.exchange(eventsDLQExchangeName).durable(true).type("topic") + ); + final Mono declareDLQ = creator.declareDLQ( + queueName, retryExchangeName, retryDelay, maxLengthBytes + ); + final Mono declareQueue = creator.declareQueue( + queueName, eventsDLQExchangeName, maxLengthBytes + ); + final Mono bindingDLQ = creator.bind( + BindingSpecification.binding(eventsDLQExchangeName, "#", queueName + ".DLQ") + ); + final Mono retryBinding = creator.bind( + BindingSpecification.binding(retryExchangeName, "#", queueName) + ); + return declareExchange.then(retryExchange) + .then(declareExchangeDLQ) + .then(declareQueue) + .then(declareDLQ) + .thenMany(bindings) + .then(bindingDLQ) + .then(retryBinding) + .then(); } else { final Mono declareQueue = creator.declareQueue(queueName, maxLengthBytes); return declareExchange.then(declareQueue).thenMany(bindings).then(); 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 6a6e4336..2f0a7e03 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 @@ -53,28 +53,21 @@ public ApplicationNotificationListener(ReactiveMessageListener receiver, protected Mono setUpBindings(TopologyCreator creator) { final Mono declareQueue = creator.declare( - queue(queueName) - .durable(false) - .autoDelete(true) - .exclusive(true)); + queue(queueName).durable(false).autoDelete(true).exclusive(true) + ); final Flux bindings = fromIterable(resolver.getNotificationListeners()) - .flatMap(listener -> creator.bind(binding(exchangeName, listener.getPath(), queueName))); + .flatMap(listener -> creator.bind( + binding(exchangeName, listener.getPath(), queueName)) + ); if (createTopology) { - final Mono declareExchange = creator.declare(exchange(exchangeName) - .type("topic") - .durable(true)); - - return declareExchange + return creator.declare(exchange(exchangeName).type("topic").durable(true)) .then(declareQueue) .thenMany(bindings) .then(); - } else { - return declareQueue - .thenMany(bindings) - .then(); } + return declareQueue.thenMany(bindings).then(); } @Override @@ -84,15 +77,12 @@ protected Function> rawMessageHandler(String executorPath) Function converter = resolveConverter(eventListener); final EventExecutor executor = new EventExecutor<>(eventListener.getHandler(), converter); - return message -> executor - .execute(message) - .cast(Object.class); + return message -> executor.execute(message).cast(Object.class); } @Override protected String getExecutorPath(AcknowledgableDelivery message) { - return message.getEnvelope() - .getRoutingKey(); + return message.getEnvelope().getRoutingKey(); } @Override diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java index 90c80154..142ee1a9 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java @@ -66,7 +66,8 @@ public ApplicationQueryListener(ReactiveMessageListener listener, String queueNa protected Function> rawMessageHandler(String executorPath) { final RegisteredQueryHandler handler = handlerResolver.getQueryHandler(executorPath); if (handler == null) { - return message -> Mono.error(new RuntimeException("Handler Not registered for Query: " + executorPath)); + return message -> + Mono.error(new RuntimeException("Handler Not registered for Query: " + executorPath)); } Function messageConverter = resolveConverter(handler.getQueryClass()); final QueryExecutor executor = new QueryExecutor<>(handler.getHandler(), messageConverter); @@ -81,17 +82,36 @@ private Function resolveConverter(Class handlerClass) { } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(directExchange).durable(true).type("direct")); + final Mono declareExchange = creator.declare( + ExchangeSpecification.exchange(directExchange).durable(true).type("direct") + ); if (withDLQRetry) { - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(directExchange + ".DLQ").durable(true).type("direct")); - final Mono declareQueue = creator.declareQueue(queueName, directExchange + ".DLQ", maxLengthBytes); - final Mono declareDLQ = creator.declareDLQ(queueName, directExchange, retryDelay, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(directExchange + ".DLQ", queueName, queueName + ".DLQ")); - return declareExchange.then(declareExchangeDLQ).then(declareQueue).then(declareDLQ).then(binding).then(bindingDLQ).then(); + final Mono declareExchangeDLQ = creator.declare( + ExchangeSpecification.exchange(directExchange + ".DLQ").durable(true).type("direct") + ); + final Mono declareQueue = creator.declareQueue( + queueName, directExchange + ".DLQ", maxLengthBytes + ); + final Mono declareDLQ = creator.declareDLQ( + queueName, directExchange, retryDelay, maxLengthBytes + ); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, queueName, queueName) + ); + final Mono bindingDLQ = creator.bind( + BindingSpecification.binding(directExchange + ".DLQ", queueName, queueName + ".DLQ") + ); + return declareExchange + .then(declareExchangeDLQ) + .then(declareQueue) + .then(declareDLQ) + .then(binding) + .then(bindingDLQ) + .then(); } else { final Mono declareQueue = creator.declareQueue(queueName, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, queueName, queueName)); return declareExchange.then(declareQueue).then(binding).then(); } } 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 27811aa5..82e32ac1 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 @@ -33,6 +33,7 @@ import static reactor.core.publisher.Mono.defer; @Log +@SuppressWarnings({"unchecked", "rawtypes"}) public abstract class GenericMessageListener { public static final int DEFAULT_RETRIES_DLQ = 10; private final ConcurrentHashMap>> handlers = new ConcurrentHashMap<>(); @@ -51,9 +52,10 @@ public abstract class GenericMessageListener { private final CustomReporter customReporter; private volatile Flux messageFlux; - public GenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, - boolean createTopology, long maxRetries, long retryDelay, DiscardNotifier discardNotifier, - String objectType, CustomReporter customReporter) { + protected GenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, + boolean createTopology, long maxRetries, long retryDelay, + DiscardNotifier discardNotifier, String objectType, + CustomReporter customReporter) { this.receiver = listener.getReceiver(); this.queueName = queueName; this.messageListener = listener; @@ -79,9 +81,13 @@ protected Mono setUpBindings(TopologyCreator creator) { } public void startListener() { - log.log(Level.INFO, "Using max concurrency {0}, in queue: {1}", new Object[]{messageListener.getMaxConcurrency(), queueName}); + log.log(Level.INFO, "Using max concurrency {0}, in queue: {1}", + new Object[]{messageListener.getMaxConcurrency(), queueName} + ); if (useDLQRetries) { - log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", new Object[]{maxRetries}); + log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", + new Object[]{maxRetries} + ); } else { log.log(Level.INFO, "ATTENTION! Using infinite fast retries as Retry Strategy"); } @@ -99,7 +105,6 @@ public void startListener() { .transform(this::consumeFaultTolerant); } - onTerminate(); } @@ -175,7 +180,9 @@ private void doLogExecution(String executorPath, long timeElapsed) { protected void logError(Throwable err, AcknowledgableDelivery msj, FallbackStrategy strategy) { String messageID = msj.getProperties().getMessageId(); try { - log.log(Level.SEVERE, format("Error encounter while processing message %s: %s", messageID, err.toString()), err); + log.log(Level.SEVERE, + format("Error encounter while processing message %s: %s", messageID, err.toString()), err + ); log.warning(format("Message %s Headers: %s", messageID, msj.getProperties().getHeaders().toString())); log.warning(format("Message %s Body: %s", messageID, new String(msj.getBody()))); } catch (Exception e) { diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java index 6736ed1a..280a84e9 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java @@ -14,7 +14,7 @@ public static Message mockMessage() { properties.getHeaders().put(REPLY_ID, "reply"); properties.getHeaders().put(CORRELATION_ID, "correlation"); properties.getHeaders().put(SERVED_QUERY_ID, "my-query"); - return new RabbitMessage("{\"id\":\"id\",\"name\":\"name\",\"date\":\"2020-10-22T17:03:26.062Z\"}".getBytes() - , properties); + return new RabbitMessage("{\"id\":\"id\",\"name\":\"name\",\"date\":\"2020-10-22T17:03:26.062Z\"}".getBytes(), + properties); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java index a5b2dd6e..1f6137ae 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java @@ -20,15 +20,27 @@ class HandlerResolverTest { void setup() { Map> commandHandlers = new ConcurrentHashMap<>(); Map> eventListeners = new ConcurrentHashMap<>(); - eventListeners.put("event.name", new RegisteredEventListener<>("event.name", message -> Mono.empty(), String.class)); - eventListeners.put("event.name2", new RegisteredEventListener<>("event.name2", message -> Mono.empty(), String.class)); - eventListeners.put("some.*", new RegisteredEventListener<>("some.*", message -> Mono.empty(), String.class)); + eventListeners.put("event.name", new RegisteredEventListener<>( + "event.name", message -> Mono.empty(), String.class) + ); + eventListeners.put("event.name2", new RegisteredEventListener<>( + "event.name2", message -> Mono.empty(), String.class) + ); + eventListeners.put("some.*", new RegisteredEventListener<>( + "some.*", message -> Mono.empty(), String.class) + ); Map> eventsToBind = new ConcurrentHashMap<>(); - eventsToBind.put("event.name", new RegisteredEventListener<>("event.name", message -> Mono.empty(), String.class)); - eventsToBind.put("event.name2", new RegisteredEventListener<>("event.name2", message -> Mono.empty(), String.class)); + eventsToBind.put("event.name", new RegisteredEventListener<>( + "event.name", message -> Mono.empty(), String.class) + ); + eventsToBind.put("event.name2", new RegisteredEventListener<>( + "event.name2", message -> Mono.empty(), String.class) + ); Map> notificationEventListeners = new ConcurrentHashMap<>(); Map> queryHandlers = new ConcurrentHashMap<>(); - resolver = new HandlerResolver(queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers); + resolver = new HandlerResolver( + queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers + ); } @Test diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java index d55addd5..05c408a5 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java @@ -68,14 +68,14 @@ class RabbitDirectAsyncGatewayTest { private final MeterRegistry meterRegistry = new SimpleMeterRegistry(); private RabbitDirectAsyncGateway asyncGateway; - public void init(ReactiveMessageSender sender) { + void init(ReactiveMessageSender sender) { asyncGateway = new RabbitDirectAsyncGateway(config, router, sender, "exchange", converter, meterRegistry); } @Test void shouldReleaseRouterResourcesOnTimeout() { - BrokerConfig config = new BrokerConfig(false, false, false, Duration.ofSeconds(1)); - asyncGateway = new RabbitDirectAsyncGateway(config, router, senderMock, "ex", converter, meterRegistry); + BrokerConfig brokerConfig = new BrokerConfig(false, false, false, Duration.ofSeconds(1)); + asyncGateway = new RabbitDirectAsyncGateway(brokerConfig, router, senderMock, "ex", converter, meterRegistry); when(router.register(anyString())).thenReturn(Mono.never()); when(senderMock.sendNoConfirm(any(), anyString(), anyString(), anyMap(), anyBoolean())) .thenReturn(Mono.empty()); @@ -98,8 +98,10 @@ void shouldSendInOptimalTime() throws InterruptedException { int messageCount = 40000; final Flux> messages = createMessagesHot(messageCount); final Flux target = - messages.flatMap(dummyMessageCommand -> asyncGateway.sendCommand(dummyMessageCommand, "testTarget") - .doOnSuccess(aVoid -> semaphore.release())); + messages.flatMap(dummyMessageCommand -> + asyncGateway.sendCommand(dummyMessageCommand, "testTarget") + .doOnSuccess(aVoid -> semaphore.release()) + ); final long init = System.currentTimeMillis(); target.subscribe(); @@ -118,7 +120,7 @@ void shouldSendInOptimalTime() throws InterruptedException { @Test @SuppressWarnings("unchecked") - public void shouldReplyQuery() { + void shouldReplyQuery() { // Arrange senderMock(); @@ -132,13 +134,15 @@ public void shouldReplyQuery() { StepVerifier.create(result).verifyComplete(); ArgumentCaptor> headersCaptor = ArgumentCaptor.forClass(Map.class); verify(senderMock, times(1)) - .sendNoConfirm(eq(response), eq("globalReply"), eq("replyId"), headersCaptor.capture(), anyBoolean()); + .sendNoConfirm(eq(response), eq("globalReply"), eq("replyId"), + headersCaptor.capture(), anyBoolean() + ); assertThat(headersCaptor.getValue().get(CORRELATION_ID)).isEqualTo("correlationId"); } @Test @SuppressWarnings("unchecked") - public void shouldReplyQueryWithout() { + void shouldReplyQueryWithout() { // Arrange senderMock(); @@ -158,7 +162,7 @@ public void shouldReplyQueryWithout() { @Test @SuppressWarnings("unchecked") - public void shouldHandleRequestReply() throws JsonProcessingException { + void shouldHandleRequestReply() throws JsonProcessingException { // Arrange senderMock(); mockReply(); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java index 536cb968..f93864ab 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java @@ -16,7 +16,9 @@ class RabbitMessageTest { void shouldCreateFromDelivery() { Envelope env = new Envelope(2, false, "exchange", "routeKey"); Map headers = new HashMap<>(); - AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().contentType("content").contentEncoding("utf8").headers(headers).build(); + AMQP.BasicProperties props = new AMQP.BasicProperties.Builder() + .contentType("content").contentEncoding("utf8").headers(headers) + .build(); byte[] body = new byte[]{3, 4, 5, 6}; Delivery delivery = new Delivery(env, props, body); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java index 1dd463d8..95e74a4e 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java @@ -25,10 +25,9 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) +@SuppressWarnings("unchecked") class ReactiveMessageSenderTest { - private final String sourceApplication = "TestApp"; - private ReactiveMessageSender messageSender; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -43,12 +42,11 @@ class ReactiveMessageSenderTest { public void init() { when(sender.sendWithTypedPublishConfirms(any(Publisher.class))).then(invocation -> { final Flux argument = invocation.getArgument(0); - return argument.map(myOutboundMessage -> { - OutboundMessageResult outboundMessageResult = new OutboundMessageResult<>(myOutboundMessage, true); - return outboundMessageResult; - }); + return argument + .map(myOutboundMessage -> new OutboundMessageResult<>(myOutboundMessage, true)); }); when(sender.send(any(Publisher.class))).thenReturn(Mono.empty()); + String sourceApplication = "TestApp"; messageSender = new ReactiveMessageSender(sender, sourceApplication, messageConverter, null); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java index a12f0d37..2136f319 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java @@ -1,6 +1,5 @@ package org.reactivecommons.async.rabbit.converters.json; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; @@ -8,12 +7,10 @@ import static org.assertj.core.api.Assertions.assertThat; -public class CloudEventBuilderExtTest { - - private final ObjectMapper objectMapper = new ObjectMapper(); +class CloudEventBuilderExtTest { @Test - void asBytes(){ + void asBytes() { Date date = new Date(); SampleClass result = new SampleClass("35", "name1", date); byte[] arrayByte = CloudEventBuilderExt.asBytes(result); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java index 0376ae96..15b697b4 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java @@ -69,7 +69,9 @@ void readCloudEvent() { .withId(UUID.randomUUID().toString()) // .withSource(URI.create("https://spring.io/foos"))// .withType("command") - .withData("application/json", JsonCloudEventData.wrap(objectMapper.valueToTree(new SampleClass("35", "name1", date)))) + .withData("application/json", JsonCloudEventData.wrap( + objectMapper.valueToTree(new SampleClass("35", "name1", date))) + ) .build(); Message message = converter.toMessage(command); CloudEvent result = converter.readCloudEvent(message); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java index c8df1070..0b12f4fc 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java @@ -51,7 +51,7 @@ class ApplicationCommandListenerPerfTest { private static final CountDownLatch latch = new CountDownLatch(12 + 1); - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private final Semaphore semaphore = new Semaphore(0); @Mock private Receiver receiver; @@ -62,7 +62,9 @@ class ApplicationCommandListenerPerfTest { @Mock private CustomReporter errorReporter; private StubGenericMessageListener messageListener; - private MessageConverter messageConverter = new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + private final MessageConverter messageConverter = new RabbitJacksonMessageConverter( + new DefaultObjectMapperSupplier().get() + ); private ReactiveMessageListener reactiveMessageListener; private static BigInteger makeHardWork() { @@ -98,19 +100,21 @@ void shouldProcessMessagesInOptimalTime() throws JsonProcessingException, Interr HandlerResolver handlerResolver = createHandlerResolver(HandlerRegistry.register() .handleCommand("app.command.test", this::handleTestMessage, DummyMessage.class) ); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); - Flux messageFlux = createSource(messageCount); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter + ); + Flux messageFlux = createSource(MESSAGE_COUNT); TestUtils.instructSafeReceiverMock(receiver, messageFlux); messageListener.startListener(); final long init = System.currentTimeMillis(); latch.countDown(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - final double microsPerMessage = ((total + 0.0) / messageCount) * 1000; - System.out.println("Message count: " + messageCount); + final double microsPerMessage = ((total + 0.0) / MESSAGE_COUNT) * 1000; + System.out.println("Message count: " + MESSAGE_COUNT); System.out.println("Total Execution Time: " + total + "ms"); System.out.println("Microseconds per message: " + microsPerMessage + "us"); if (System.getProperty("env.ci") == null) { @@ -127,18 +131,20 @@ void shouldProcessAsyncMessagesConcurrent() throws JsonProcessingException, Inte HandlerResolver handlerResolver = createHandlerResolver(HandlerRegistry.register() .handleCommand("app.command.test", this::handleTestMessageDelay, DummyMessage.class) ); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); - Flux messageFlux = createSource(messageCount); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter + ); + Flux messageFlux = createSource(MESSAGE_COUNT); TestUtils.instructSafeReceiverMock(receiver, messageFlux); System.out.println("Permits before: " + semaphore.availablePermits()); final long init = System.currentTimeMillis(); messageListener.startListener(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - final double microsPerMessage = ((total + 0.0) / messageCount) * 1000; - System.out.println("Message count: " + messageCount); + final double microsPerMessage = ((total + 0.0) / MESSAGE_COUNT) * 1000; + System.out.println("Message count: " + MESSAGE_COUNT); System.out.println("Total Execution Time: " + total + "ms"); System.out.println("Microseconds per message: " + microsPerMessage + "us"); if (System.getProperty("env.ci") == null) { @@ -183,7 +189,9 @@ void shouldProcessCPUMessagesInParallel() throws JsonProcessingException, Interr ); int messageCount = 2000; reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator, 250, 250); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter + ); Flux messageFlux = createSource(messageCount); TestUtils.instructSafeReceiverMock(receiver, messageFlux); @@ -211,7 +219,8 @@ void shouldProcessCPUWorkMessagesInParallel() throws JsonProcessingException, In ); int messageCount = 2000; reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator, 500, 250); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter); Flux messageFlux = createSource(messageCount); TestUtils.instructSafeReceiverMock(receiver, messageFlux); @@ -239,7 +248,8 @@ void shouldProcessPasiveBlockingMessagesInParallel() throws JsonProcessingExcept ); int messageCount = 2000; reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator, 500, 250); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter); Flux messageFlux = createSource(messageCount); TestUtils.instructSafeReceiverMock(receiver, messageFlux); @@ -261,20 +271,25 @@ void shouldProcessPasiveBlockingMessagesInParallel() throws JsonProcessingExcept } private HandlerResolver createHandlerResolver(final HandlerRegistry initialRegistry) { - final HandlerRegistry registry = range(0, 20).reduce(initialRegistry, (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); - final ConcurrentMap> commandHandlers = registry.getCommandHandlers().stream() - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); + final HandlerRegistry registry = range(0, 20) + .reduce(initialRegistry, (r, i) -> + r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class) + ) + .block(); + final ConcurrentMap> commandHandlers = registry.getCommandHandlers() + .stream() + .collect(ConcurrentHashMap::new, (map, handler) -> + map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); return new HandlerResolver(null, null, null, null, commandHandlers) { @Override @SuppressWarnings("unchecked") public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = (RegisteredCommandHandler) super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler>("", new DefaultCommandHandler() { - @Override - public Mono handle(Command message) { - return Mono.error(new RuntimeException("Default handler in Test")); - } - }, Object.class); + final RegisteredCommandHandler handler = super.getCommandHandler(path); + return handler != null ? handler : new RegisteredCommandHandler<>( + "", (DefaultCommandHandler) message -> + Mono.error(new RuntimeException("Default handler in Test")), Object.class + ); } }; } @@ -282,7 +297,9 @@ public Mono handle(Command message) { private Flux createSource(int count) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); - Command command = new Command<>("app.command.test", UUID.randomUUID().toString(), new DummyMessage()); + Command command = new Command<>( + "app.command.test", UUID.randomUUID().toString(), new DummyMessage() + ); String data = mapper.writeValueAsString(command); final List list = IntStream.range(0, count).mapToObj(value -> { AMQP.BasicProperties props = new AMQP.BasicProperties(); @@ -312,10 +329,14 @@ private AMQP.BasicProperties createProps() { null); } - class StubGenericMessageListener extends ApplicationCommandListener { + static class StubGenericMessageListener extends ApplicationCommandListener { - public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, long maxRetries, DiscardNotifier discardNotifier, String objectType, HandlerResolver handlerResolver, MessageConverter messageConverter, CustomReporter errorReporter) { - super(listener, queueName, handlerResolver, "directExchange", messageConverter, true, false, false, 10, 10, Optional.empty(), discardNotifier, errorReporter); + public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, + long maxRetries, DiscardNotifier discardNotifier, String objectType, + HandlerResolver handlerResolver, MessageConverter messageConverter, + CustomReporter errorReporter) { + super(listener, queueName, handlerResolver, "directExchange", messageConverter, true, false, false, 10, 10, + Optional.empty(), discardNotifier, errorReporter); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java index e5f63737..fa72d55a 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java @@ -17,31 +17,45 @@ import static reactor.core.publisher.Mono.error; @ExtendWith(MockitoExtension.class) -public class ApplicationCommandListenerTest extends ListenerReporterTestSuperClass{ +@SuppressWarnings("unchecked") +public class ApplicationCommandListenerTest extends ListenerReporterTestSuperClass { - private final Command command = new Command<>("app.command.test", UUID.randomUUID().toString(), new DummyMessage()); - private final Command command2 = new Command<>("app.command.test2", UUID.randomUUID().toString(), new DummyMessage()); + private final Command command = new Command<>( + "app.command.test", UUID.randomUUID().toString(), new DummyMessage() + ); + private final Command command2 = new Command<>( + "app.command.test2", UUID.randomUUID().toString(), new DummyMessage() + ); @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .handleCommand("app.command.test", m -> + error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(Command::getName, command)); } @Test void shouldSendErrorMetricToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .handleCommand("app.command.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(Command::getName, command)); - verify(errorReporter).reportMetric(eq("command"), eq("app.command.test"), longThat(time -> time >= 0 ), eq(false)); + verify(errorReporter) + .reportMetric(eq("command"), eq("app.command.test"), longThat(time -> time >= 0), eq(false)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .handleCommand("app.command.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .handleCommand("app.command.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .handleCommand("app.command.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(Command::getName, command, command2)); } @@ -54,7 +68,8 @@ protected GenericMessageListener createMessageListener(HandlerResolver handlerRe class StubGenericMessageListener extends ApplicationCommandListener { public StubGenericMessageListener(HandlerResolver handlerResolver) { - super(reactiveMessageListener, "queueName", handlerResolver, "directExchange", messageConverter, true, false,false,10, 10, Optional.empty(), discardNotifier, errorReporter); + super(reactiveMessageListener, "queueName", handlerResolver, "directExchange", messageConverter, true, + false, false, 10, 10, Optional.empty(), discardNotifier, errorReporter); } } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java index 62daa8a1..763785f7 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java @@ -14,23 +14,35 @@ import static reactor.core.publisher.Mono.error; @ExtendWith(MockitoExtension.class) +@SuppressWarnings("unchecked") public class ApplicationEventListenerTest extends ListenerReporterTestSuperClass { - private DomainEvent event1 = new DomainEvent<>("app.event.test", UUID.randomUUID().toString(), new DummyMessage()); - private DomainEvent event2 = new DomainEvent<>("app.event.test2", UUID.randomUUID().toString(), new DummyMessage()); + private final DomainEvent event1 = new DomainEvent<>( + "app.event.test", UUID.randomUUID().toString(), new DummyMessage() + ); + + private final DomainEvent event2 = new DomainEvent<>( + "app.event.test2", UUID.randomUUID().toString(), new DummyMessage() + ); @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .listenEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .listenEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(DomainEvent::getName, event1)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .listenEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .listenEvent("app.event.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .listenEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .listenEvent("app.event.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, event2)); } @@ -42,7 +54,8 @@ protected GenericMessageListener createMessageListener(HandlerResolver handlerRe class StubGenericMessageListener extends ApplicationEventListener { public StubGenericMessageListener(HandlerResolver handlerResolver) { - super(reactiveMessageListener, "queueName", "domainEvents", handlerResolver, messageConverter, true, true,10, 10, Optional.empty(), discardNotifier, errorReporter, ""); + super(reactiveMessageListener, "queueName", "domainEvents", handlerResolver, messageConverter, true, true, + 10, 10, Optional.empty(), discardNotifier, errorReporter, ""); } } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java index 8b146860..afcc5fff 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java @@ -22,8 +22,12 @@ @ExtendWith(MockitoExtension.class) public class ApplicationNotificationListenerTest extends ListenerReporterTestSuperClass { - private DomainEvent event1 = new DomainEvent<>("app.event.test", UUID.randomUUID().toString(), new DummyMessage()); - private DomainEvent event2 = new DomainEvent<>("app.event.test2", UUID.randomUUID().toString(), new DummyMessage()); + private final DomainEvent event1 = new DomainEvent<>( + "app.event.test", UUID.randomUUID().toString(), new DummyMessage() + ); + private final DomainEvent event2 = new DomainEvent<>( + "app.event.test2", UUID.randomUUID().toString(), new DummyMessage() + ); @BeforeEach public void initCreator() { @@ -33,15 +37,20 @@ public void initCreator() { @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .listenNotificationEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .listenNotificationEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class); assertSendErrorToCustomReporter(registry, createSource(DomainEvent::getName, event1)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .listenNotificationEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .listenNotificationEvent("app.event.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .listenNotificationEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .listenNotificationEvent("app.event.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, event2)); } @@ -54,7 +63,8 @@ protected GenericMessageListener createMessageListener(HandlerResolver handlerRe class StubGenericMessageListener extends ApplicationNotificationListener { public StubGenericMessageListener(HandlerResolver handlerResolver) { - super(reactiveMessageListener, "exchange", "queue", true, handlerResolver, messageConverter, discardNotifier, errorReporter); + super(reactiveMessageListener, "exchange", "queue", true, handlerResolver, + messageConverter, discardNotifier, errorReporter); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java index b5ec8c32..a2ed493c 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java @@ -17,21 +17,27 @@ @ExtendWith(MockitoExtension.class) public class ApplicationQueryListenerErrorTest extends ListenerReporterTestSuperClass { - private AsyncQuery event1 = new AsyncQuery<>("app.query.test", new DummyMessage()); - private AsyncQuery event2 = new AsyncQuery<>("app.query.test2", new DummyMessage()); + private final AsyncQuery event1 = new AsyncQuery<>("app.query.test", new DummyMessage()); + private final AsyncQuery event2 = new AsyncQuery<>("app.query.test2", new DummyMessage()); @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .serveQuery("app.query.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .serveQuery("app.query.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(AsyncQuery::getResource, event1)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .serveQuery("app.query.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .serveQuery("app.query.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .serveQuery("app.query.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .serveQuery("app.query.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(AsyncQuery::getResource, event1, event2)); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java index f7dd0377..1016683b 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java @@ -2,24 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.BuiltinExchangeType; -import com.rabbitmq.client.CancelCallback; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.ConfirmCallback; -import com.rabbitmq.client.ConfirmListener; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.ConsumerShutdownSignalCallback; -import com.rabbitmq.client.DeliverCallback; -import com.rabbitmq.client.Delivery; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.Method; -import com.rabbitmq.client.ReturnCallback; -import com.rabbitmq.client.ReturnListener; -import com.rabbitmq.client.ShutdownListener; -import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.*; import lombok.Data; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -78,10 +61,12 @@ class GenericMessageListenerPerfTest { private final Semaphore semaphore = new Semaphore(0); @BeforeEach - public void init() { + void init() { // when(errorReporter.reportError(any(Throwable.class), any(Message.class), any(Object.class))).thenReturn(Mono.empty()); ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, true,10, discardNotifier, "command", errorReporter); + messageListener = new StubGenericMessageListener( + "test-queue", reactiveMessageListener, true, true, 10, discardNotifier, "command", errorReporter + ); } @@ -124,7 +109,9 @@ void referenceTime() throws JsonProcessingException, InterruptedException { private Flux createSource(int count) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); - Command command = new Command<>("some.command.name", UUID.randomUUID().toString(), new DummyMessage()); + Command command = new Command<>( + "some.command.name", UUID.randomUUID().toString(), new DummyMessage() + ); String data = mapper.writeValueAsString(command); final List list = IntStream.range(0, count).mapToObj(value -> { AMQP.BasicProperties props = new AMQP.BasicProperties(); @@ -156,8 +143,11 @@ private AMQP.BasicProperties createProps() { class StubGenericMessageListener extends GenericMessageListener { - public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, boolean createTopology, long maxRetries, DiscardNotifier discardNotifier, String objectType, CustomReporter errorReporter) { - super(queueName, listener, useDLQRetries, createTopology, maxRetries, 200, discardNotifier, objectType, errorReporter); + public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, + boolean createTopology, long maxRetries, DiscardNotifier discardNotifier, + String objectType, CustomReporter errorReporter) { + super(queueName, listener, useDLQRetries, createTopology, maxRetries, 200, discardNotifier, + objectType, errorReporter); } @Override @@ -580,7 +570,10 @@ public String basicConsume(String queue, boolean autoAck, String consumerTag, bo } @Override - public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, + Map arguments, DeliverCallback deliverCallback, + CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { return null; } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java index b70a2196..2cfeb669 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java @@ -54,28 +54,41 @@ import static reactor.core.publisher.Mono.empty; import static reactor.core.publisher.Mono.just; +@SuppressWarnings("unchecked") public abstract class ListenerReporterTestSuperClass { protected final TopologyCreator topologyCreator = mock(TopologyCreator.class); protected final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - protected final MessageConverter messageConverter = new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + protected final MessageConverter messageConverter = new RabbitJacksonMessageConverter( + new DefaultObjectMapperSupplier().get() + ); protected final CustomReporter errorReporter = mock(CustomReporter.class); protected final Semaphore semaphore = new Semaphore(0); protected final Semaphore successSemaphore = new Semaphore(0); private final ObjectMapper mapper = new ObjectMapper(); private final Receiver receiver = mock(Receiver.class); - protected final ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); + protected final ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener( + receiver, topologyCreator + ); private GenericMessageListener messageListener; @BeforeEach public void init() { - Mockito.when(topologyCreator.declare(any(ExchangeSpecification.class))).thenReturn(just(mock(AMQP.Exchange.DeclareOk.class))); - Mockito.when(topologyCreator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any(Optional.class))).thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); - Mockito.when(topologyCreator.declareQueue(any(String.class), any(String.class), any(Optional.class))).thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); - Mockito.when(topologyCreator.bind(any(BindingSpecification.class))).thenReturn(just(mock(AMQP.Queue.BindOk.class))); + Mockito.when(topologyCreator.declare(any(ExchangeSpecification.class))) + .thenReturn(just(mock(AMQP.Exchange.DeclareOk.class))); + Mockito.when(topologyCreator.declareDLQ + (any(String.class), any(String.class), any(Integer.class), any(Optional.class)) + ) + .thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); + Mockito.when(topologyCreator.declareQueue(any(String.class), any(String.class), any(Optional.class))) + .thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); + Mockito.when(topologyCreator.bind(any(BindingSpecification.class))) + .thenReturn(just(mock(AMQP.Queue.BindOk.class))); } - protected void assertContinueAfterSendErrorToCustomReporter(HandlerRegistry handlerRegistry, Flux source) throws InterruptedException { + protected void assertContinueAfterSendErrorToCustomReporter(HandlerRegistry handlerRegistry, + Flux source) + throws InterruptedException { final HandlerResolver handlerResolver = createHandlerResolver(handlerRegistry); when(errorReporter.reportError(any(Throwable.class), any(Message.class), any(Object.class), any(Boolean.class))) .then(inv -> empty().doOnSuccess(o -> semaphore.release())); @@ -93,7 +106,8 @@ protected void assertContinueAfterSendErrorToCustomReporter(HandlerRegistry hand assertThat(processed).isTrue(); } - protected void assertSendErrorToCustomReporter(HandlerRegistry handlerRegistry, Flux source) throws InterruptedException { + protected void assertSendErrorToCustomReporter(HandlerRegistry handlerRegistry, Flux source) + throws InterruptedException { final HandlerResolver handlerResolver = createHandlerResolver(handlerRegistry); when(errorReporter.reportError(any(Throwable.class), any(Message.class), any(Object.class), any(Boolean.class))) .then(inv -> empty().doOnSuccess(o -> semaphore.release())); @@ -107,7 +121,8 @@ protected void assertSendErrorToCustomReporter(HandlerRegistry handlerRegistry, ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); final boolean reported = semaphore.tryAcquire(1, TimeUnit.SECONDS); assertThat(reported).isTrue(); - verify(errorReporter).reportError(throwableCaptor.capture(), any(Message.class), any(Object.class), any(Boolean.class)); + verify(errorReporter + ).reportError(throwableCaptor.capture(), any(Message.class), any(Object.class), any(Boolean.class)); assertThat(throwableCaptor.getValue().getMessage()).isEqualTo("testEx"); } @@ -119,7 +134,9 @@ protected Flux createSource(Function rout .headers(Collections.singletonMap(Headers.SERVED_QUERY_ID, routeExtractor.apply(value))) .build(); - final Envelope envelope = new Envelope(new Random().nextInt(), true, "exchange", routeExtractor.apply(value)); + final Envelope envelope = new Envelope( + new Random().nextInt(), true, "exchange", routeExtractor.apply(value) + ); final Delivery delivery = new Delivery(envelope, props, data.getBytes()); return new AcknowledgableDelivery(delivery, new ChannelDummy(), null); }).collect(Collectors.toList()); @@ -130,11 +147,18 @@ protected Flux createSource(Function rout protected abstract GenericMessageListener createMessageListener(final HandlerResolver handlerResolver); private HandlerResolver createHandlerResolver(final HandlerRegistry registry) { - final Map> eventHandlers = Stream.concat(registry.getDynamicEventHandlers().stream(), registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()).collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> eventsToBind = registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> notificationHandlers = registry.getEventNotificationListener().stream().collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> queryHandlers = registry.getHandlers().stream().collect(toMap(RegisteredQueryHandler::getPath, identity())); - final Map> commandHandlers = registry.getCommandHandlers().stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); + final Map> eventHandlers = Stream.concat( + registry.getDynamicEventHandlers().stream(), + registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()) + .collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> eventsToBind = registry.getDomainEventListeners() + .get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> notificationHandlers = registry.getEventNotificationListener() + .stream().collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> queryHandlers = registry.getHandlers().stream() + .collect(toMap(RegisteredQueryHandler::getPath, identity())); + final Map> commandHandlers = registry.getCommandHandlers() + .stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); return new HandlerResolver( new ConcurrentHashMap<>(queryHandlers), new ConcurrentHashMap<>(eventHandlers), diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java index 10e3079f..c42709fe 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java @@ -1,6 +1,7 @@ package org.reactivecommons.async.utils; -import lombok.experimental.UtilityClass; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import reactor.core.publisher.Flux; import reactor.rabbitmq.AcknowledgableDelivery; import reactor.rabbitmq.ConsumeOptions; @@ -14,8 +15,8 @@ import static reactor.core.publisher.Flux.defer; -@UtilityClass -public class TestUtils { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class TestUtils { public static void instructSafeReceiverMock(final Receiver receiver, final Flux source) { final AtomicReference> sourceReference = new AtomicReference<>(source); diff --git a/build.gradle b/build.gradle index 4a898c00..669fdc4a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ buildscript { repositories { mavenCentral() - maven { url 'https://repo.spring.io/milestone' } - maven { url 'https://repo.spring.io/snapshot' } + maven { url = 'https://repo.spring.io/milestone' } + maven { url = 'https://repo.spring.io/snapshot' } } dependencies { diff --git a/docs/docs/reactive-commons/2-sending-a-domain-event.md b/docs/docs/reactive-commons/2-sending-a-domain-event.md index 243e1a72..08cb8703 100644 --- a/docs/docs/reactive-commons/2-sending-a-domain-event.md +++ b/docs/docs/reactive-commons/2-sending-a-domain-event.md @@ -40,7 +40,7 @@ For example: @EnableDomainEventBus public class ReactiveEventsGateway { public static final String SOME_EVENT_NAME = "some.event.name"; - private final DomainEventBus domainEventBus; // Auto injectec bean created by the @EnableDomainEventBus annotation + private final DomainEventBus domainEventBus; // Auto injected bean created by the @EnableDomainEventBus annotation public Mono emit(Object event) { return Mono.from(domainEventBus.emit(new DomainEvent<>(SOME_EVENT_NAME, UUID.randomUUID().toString(), event))); diff --git a/docs/docs/reactive-commons/4-making-an-async-query.md b/docs/docs/reactive-commons/4-making-an-async-query.md index 6c1d15f5..a8f13541 100644 --- a/docs/docs/reactive-commons/4-making-an-async-query.md +++ b/docs/docs/reactive-commons/4-making-an-async-query.md @@ -52,7 +52,7 @@ For example: public class ReactiveDirectAsyncGateway { public static final String TARGET_NAME = "other-app";// refers to remote spring.application.name property public static final String SOME_QUERY_NAME = "some.query.name"; - private final DirectAsyncGateway gateway; // Auto injectec bean created by the @EnableDirectAsyncGateway annotation + private final DirectAsyncGateway gateway; // Auto injected bean created by the @EnableDirectAsyncGateway annotation public Mono requestForRemoteData(Object query/*change for proper model*/) { return gateway.requestReply(new AsyncQuery<>(SOME_QUERY_NAME, query), TARGET_NAME, Object.class/*change for proper model*/); diff --git a/docs/docs/reactive-commons/8-serving-async-queries.md b/docs/docs/reactive-commons/8-serving-async-queries.md index fc0fff41..f6b94b89 100644 --- a/docs/docs/reactive-commons/8-serving-async-queries.md +++ b/docs/docs/reactive-commons/8-serving-async-queries.md @@ -38,7 +38,7 @@ As the model of queries is direct, a consumer always can send queries to the ser ### Wildcards -You may need to handle variable querie names that have the same structure, in that case you can specfy a pattern with '*' wildcard, for example: +You may need to handle variable queries names that have the same structure, in that case you can specfy a pattern with '*' wildcard, for example: ```java @Configuration @@ -60,7 +60,7 @@ There is a concept introduced in queries that cannot be resolved locally and may - A consumer application called APP1 make an async query to an application called APP2 (with horizontal scaling). - an instance A of the APP2 receives the query and stores a reference to respond later. -- When response is fullfilled, an external source makes an HTTPS Call to an endpoint of an instance of APP2 but it can be diferent to the instance A, for example can be the instance B. +- When response is fulfilled, an external source makes an HTTPS Call to an endpoint of an instance of APP2 but it can be diferent to the instance A, for example can be the instance B. - The instance B of APP2 queries for the saved reference and uses DirectAsyncGateway to answer the pending query to the application APP1. This scenario can be resolved with ReactiveCommons by using the next resources: @@ -106,7 +106,7 @@ When some external source notifies our APP2 instance with the answer we should f @RequiredArgsConstructor @EnableDirectAsyncGateway public class ReactiveDirectAsyncGateway { - private final DirectAsyncGateway gateway; // Auto injectec bean created by the @EnableDirectAsyncGateway annotation + private final DirectAsyncGateway gateway; // Auto injected bean created by the @EnableDirectAsyncGateway annotation public Mono replyDelegate(String correlationId, Object response/*change for proper model*/) { return getReference(correlationId) diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index a0da9bd8..8baefe62 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -62,7 +62,7 @@ app: virtual-host: /accounts ``` -You can override this settings programmatically through a `AsyncPropsDomainProperties` bean. +You can override this settings programmatically through a `AsyncRabbitPropsDomainProperties` bean. ```java package sample; @@ -78,7 +78,7 @@ public class MyDomainConfig { @Bean @Primary - public AsyncPropsDomainProperties customDomainProperties() { + public AsyncRabbitPropsDomainProperties customDomainProperties() { RabbitProperties propertiesApp = new RabbitProperties(); propertiesApp.setHost("localhost"); propertiesApp.setPort(5672); @@ -93,7 +93,7 @@ public class MyDomainConfig { propertiesAccounts.setUsername("guest"); propertiesAccounts.setPassword("guest"); - return AsyncPropsDomainProperties.builder() + return AsyncRabbitPropsDomainProperties.builder() .withDomain("app", AsyncProps.builder() .connectionProperties(propertiesApp) .build()) diff --git a/docs/docs/scenarios/1-single-broker.md b/docs/docs/scenarios/1-single-broker.md index 1dbafcf7..ea4d7380 100644 --- a/docs/docs/scenarios/1-single-broker.md +++ b/docs/docs/scenarios/1-single-broker.md @@ -60,7 +60,7 @@ app: virtual-host: / ``` -You can override this settings programmatically through a `AsyncPropsDomainProperties` bean. +You can override this settings programmatically through a `AsyncRabbitPropsDomainProperties` bean. ```java package sample; @@ -76,7 +76,7 @@ public class MyDomainConfig { @Bean @Primary - public AsyncPropsDomainProperties customDomainProperties() { + public AsyncRabbitPropsDomainProperties customDomainProperties() { RabbitProperties propertiesApp = new RabbitProperties(); propertiesApp.setHost("localhost"); propertiesApp.setPort(5672); @@ -84,7 +84,7 @@ public class MyDomainConfig { propertiesApp.setUsername("guest"); propertiesApp.setPassword("guest"); - return AsyncPropsDomainProperties.builder() + return AsyncRabbitPropsDomainProperties.builder() .withDomain("app", AsyncProps.builder() .connectionProperties(propertiesApp) .build()) @@ -93,13 +93,13 @@ public class MyDomainConfig { } ``` -Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.SecretFiller` class. +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.RabbitSecretFiller` class. ```java @Bean @Primary -public AsyncPropsDomain.SecretFiller customFiller() { +public AsyncPropsDomain.RabbitSecretFiller customFiller() { return (domain, asyncProps) -> { // customize asyncProps here by domain }; diff --git a/docs/docs/scenarios/2-two-brokers-same-type.md b/docs/docs/scenarios/2-two-brokers-same-type.md index 2874565a..4ff5045c 100644 --- a/docs/docs/scenarios/2-two-brokers-same-type.md +++ b/docs/docs/scenarios/2-two-brokers-same-type.md @@ -51,7 +51,7 @@ public HandlerRegistry handlerRegistrySubs(UseCase useCase) { ```java @Service -@AllArgsConstructor +@RequiredArgsConstructor public class SampleRestController { private final DirectAsyncGateway directAsyncGateway; @@ -126,7 +126,7 @@ app: virtual-host: /accounts ``` -You can override this settings programmatically through a `AsyncPropsDomainProperties` bean. +You can override this settings programmatically through a `AsyncRabbitPropsDomainProperties` bean. ```java package sample; @@ -142,7 +142,7 @@ public class MyDomainConfig { @Bean @Primary - public AsyncPropsDomainProperties customDomainProperties() { + public AsyncRabbitPropsDomainProperties customDomainProperties() { RabbitProperties propertiesApp = new RabbitProperties(); // this may be loaded from secrets propertiesApp.setHost("localhost"); propertiesApp.setPort(5672); @@ -157,7 +157,7 @@ public class MyDomainConfig { propertiesAccounts.setUsername("guest"); propertiesAccounts.setPassword("guest"); - return AsyncPropsDomainProperties.builder() + return AsyncRabbitPropsDomainProperties.builder() .withDomain("app", AsyncProps.builder() .connectionProperties(propertiesApp) .build()) diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java index 35aa0c23..f61956b7 100644 --- a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java @@ -1,11 +1,11 @@ package org.reactivecommons.api.domain; -import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; @Data -@AllArgsConstructor +@RequiredArgsConstructor public class Command { private final String name; private final String commandId; diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java index 23717f00..b9319690 100644 --- a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java @@ -1,10 +1,10 @@ package org.reactivecommons.api.domain; -import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; @Data -@AllArgsConstructor +@RequiredArgsConstructor public class DomainEvent { private final String name; private final String eventId; 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 458a0c0c..0c62f8a8 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,8 +5,10 @@ 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/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c82..e18bc253 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # 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 +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/main.gradle b/main.gradle index 66cffb97..4df770e4 100644 --- a/main.gradle +++ b/main.gradle @@ -2,13 +2,15 @@ allprojects { apply plugin: 'java' apply plugin: 'jacoco' - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_17 + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_17 + } repositories { mavenCentral() - maven { url 'https://repo.spring.io/snapshot' } - maven { url 'https://repo.spring.io/milestone' } + maven { url = 'https://repo.spring.io/snapshot' } + maven { url = 'https://repo.spring.io/milestone' } } if (toPublish.split(',').contains(project.name) || project.name == rootProject.name) { @@ -31,7 +33,7 @@ allprojects { } } - group 'org.reactivecommons' + group = 'org.reactivecommons' } nexusPublishing { @@ -72,7 +74,7 @@ subprojects { test { useJUnitPlatform() - if (System.getProperty("env.ci").equals("true")) { + if (System.getProperty("env.ci") == "true") { systemProperty "env.ci", System.getProperty("env.ci") } @@ -88,7 +90,7 @@ subprojects { apply plugin: 'maven-publish' apply plugin: 'signing' - group groupId + group = groupId tasks.named("jar") { enabled = true @@ -176,5 +178,5 @@ tasks.register('generateMergedReport', JacocoReport) { } tasks.named('wrapper') { - gradleVersion = '8.11.1' + gradleVersion = '8.12.1' } \ No newline at end of file diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java index 44cc8772..ba08c712 100644 --- a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java @@ -33,11 +33,15 @@ public DomainHandlers buildHandlers(ApplicationContext context, 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); - })); + 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; } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java index 13972fc8..1c15a937 100644 --- a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java @@ -1,15 +1,15 @@ package org.reactivecommons.async.starter.config.health; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.util.HashMap; import java.util.Map; @Getter @Builder -@AllArgsConstructor +@RequiredArgsConstructor public class RCHealth { private final Status status; private final Map details; diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java index 04cf1aec..da158e66 100644 --- a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java @@ -6,7 +6,9 @@ public abstract class RCHealthIndicator { public Mono health() { return doHealthCheck(RCHealth.builder()) - .onErrorResume(e -> Mono.just(RCHealth.builder().down().withDetail("error", e.getMessage()).build())); + .onErrorResume(e -> + Mono.just(RCHealth.builder().down().withDetail("error", e.getMessage()).build()) + ); } public abstract Mono doHealthCheck(RCHealth.RCHealthBuilder builder); 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 index 6b316cc3..76178b28 100644 --- 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 @@ -1,6 +1,6 @@ package org.reactivecommons.async.starter.config.health; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.reactivecommons.async.starter.broker.BrokerProvider; import org.reactivecommons.async.starter.config.ConnectionManager; @@ -10,7 +10,7 @@ import reactor.core.publisher.Mono; @Log4j2 -@AllArgsConstructor +@RequiredArgsConstructor public class ReactiveCommonsHealthIndicator extends AbstractReactiveHealthIndicator { public static final String DOMAIN = "domain"; public static final String VERSION = "version"; @@ -21,7 +21,9 @@ public class ReactiveCommonsHealthIndicator extends AbstractReactiveHealthIndica protected Mono doHealthCheck(Health.Builder builder) { return Flux.fromIterable(manager.getProviders().values()) .flatMap(BrokerProvider::healthCheck) - .reduceWith(Health::up, (health, status) -> reduceHealth((Health.Builder) health, (RCHealth) status)) + .reduceWith(Health::up, (health, status) -> + reduceHealth((Health.Builder) health, (RCHealth) status) + ) .map(b -> ((Health.Builder) b).build()); } 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 index 57f27e66..28a86de0 100644 --- 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 @@ -1,6 +1,6 @@ package org.reactivecommons.async.starter.mybroker; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.commons.HandlerResolver; @@ -10,7 +10,7 @@ import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; import reactor.core.publisher.Mono; -@AllArgsConstructor +@RequiredArgsConstructor public class MyBrokerProvider implements BrokerProvider { private final String domain; private final MyBrokerAsyncProps props; 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 index 8e4dac67..b69b8fde 100644 --- 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 @@ -30,8 +30,9 @@ void shouldCreateProps() { configured.put(OTHER, other); MyBrokerSecretFiller secretFiller = (domain, props) -> { }; - MyBrokerAsyncPropsDomain propsDomain = new MyBrokerAsyncPropsDomain(defaultAppName, defaultMyBrokerProps, configured, - secretFiller); + MyBrokerAsyncPropsDomain propsDomain = new MyBrokerAsyncPropsDomain( + defaultAppName, defaultMyBrokerProps, configured, secretFiller + ); // Act MyBrokerAsyncProps props = propsDomain.getProps(DEFAULT_DOMAIN); MyBrokerAsyncProps otherProps = propsDomain.getProps(OTHER); @@ -49,7 +50,9 @@ void shouldCreatePropsWithDefaultConnectionProperties() { MyBrokerAsyncProps propsConfigured = new MyBrokerAsyncProps(); MyBrokerAsyncPropsDomain propsDomain = MyBrokerAsyncPropsDomain.builder(MyBrokerConnProps.class, AsyncMyBrokerPropsDomainProperties.class, - (Constructor) MyBrokerAsyncPropsDomain.class.getDeclaredConstructors()[0]) + (Constructor) MyBrokerAsyncPropsDomain.class + .getDeclaredConstructors()[0] + ) .withDefaultAppName(defaultAppName) .withDefaultProperties(defaultMyBrokerProps) .withDomain(DEFAULT_DOMAIN, propsConfigured) 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 index f5d04458..3a022754 100644 --- 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 @@ -1,8 +1,8 @@ package org.reactivecommons.async.kafka; import io.micrometer.core.instrument.MeterRegistry; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.commons.DiscardNotifier; @@ -24,7 +24,7 @@ import reactor.core.publisher.Mono; @Getter -@AllArgsConstructor +@RequiredArgsConstructor public class KafkaBrokerProvider implements BrokerProvider { private final String domain; private final AsyncKafkaProps props; 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 index 0c06949a..d6ebece4 100644 --- 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 @@ -1,7 +1,7 @@ package org.reactivecommons.async.kafka; import io.micrometer.core.instrument.MeterRegistry; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.apache.kafka.clients.admin.AdminClient; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.ext.CustomReporter; @@ -20,7 +20,7 @@ import org.springframework.stereotype.Service; @Service("kafka") -@AllArgsConstructor +@RequiredArgsConstructor public class KafkaBrokerProviderFactory implements BrokerProviderFactory { private final ReactiveReplyRouter router; private final KafkaJacksonMessageConverter converter; @@ -40,7 +40,8 @@ public DiscardProvider getDiscardProvider(AsyncKafkaProps props) { } @Override - public BrokerProvider getProvider(String domain, AsyncKafkaProps props, DiscardProvider discardProvider) { + 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); 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 index 11307a8b..7ac62293 100644 --- 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 @@ -1,6 +1,6 @@ package org.reactivecommons.async.kafka; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; @@ -13,7 +13,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -@AllArgsConstructor +@RequiredArgsConstructor public class KafkaDiscardProvider implements DiscardProvider { private final AsyncKafkaProps props; private final MessageConverter 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 index c6670307..2912d72b 100644 --- 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 @@ -1,6 +1,7 @@ package org.reactivecommons.async.kafka; -import lombok.experimental.UtilityClass; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.common.serialization.ByteArrayDeserializer; import org.apache.kafka.common.serialization.ByteArraySerializer; @@ -25,8 +26,8 @@ import java.nio.file.Path; import java.util.Map; -@UtilityClass -public class KafkaSetupUtils { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class KafkaSetupUtils { public static DiscardNotifier createDiscardNotifier(ReactiveMessageSender sender, MessageConverter converter) { return new DLQDiscardNotifier(new KafkaDomainEventBus(sender), converter); @@ -52,7 +53,9 @@ public static ReactiveMessageListener createMessageListener(AsyncKafkaProps conf KafkaProperties props = config.getConnectionProperties(); props.getConsumer().setKeyDeserializer(StringDeserializer.class); props.getConsumer().setValueDeserializer(ByteArrayDeserializer.class); - ReceiverOptions receiverOptions = ReceiverOptions.create(props.buildConsumerProperties(sslBundles)); + ReceiverOptions receiverOptions = ReceiverOptions.create( + props.buildConsumerProperties(sslBundles) + ); return new ReactiveMessageListener(receiverOptions); } @@ -81,6 +84,7 @@ public static KafkaProperties readPropsFromDotEnv(Path path) throws IOException } 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); + 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/props/AsyncKafkaProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java index 55919f1e..26eefb03 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 @@ -19,6 +19,7 @@ public class AsyncKafkaProps extends GenericAsyncProps { @NestedConfigurationProperty + @Builder.Default private KafkaProperties connectionProperties = new KafkaProperties(); @NestedConfigurationProperty 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 index ed49de49..2241f3c2 100644 --- 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 @@ -1,6 +1,6 @@ package org.reactivecommons.async.kafka.health; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.apache.kafka.clients.admin.AdminClient; import org.reactivecommons.async.starter.config.health.RCHealth; @@ -11,7 +11,7 @@ import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; @Log4j2 -@AllArgsConstructor +@RequiredArgsConstructor public class KafkaReactiveHealthIndicator extends RCHealthIndicator { private final String domain; private final AdminClient adminClient; diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java index 2e4ac2ee..0beea22f 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java @@ -1,7 +1,7 @@ package org.reactivecommons.async.rabbit.standalone.config; import io.micrometer.core.instrument.MeterRegistry; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.config.BrokerConfig; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; @@ -14,7 +14,7 @@ import java.util.Base64; import java.util.UUID; -@AllArgsConstructor +@RequiredArgsConstructor public class DirectAsyncGatewayConfig { private String directMessagesExchangeName; diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java index d158d48c..959e19d9 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java @@ -1,18 +1,17 @@ package org.reactivecommons.async.rabbit.standalone.config; +import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.rabbit.RabbitDomainEventBus; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import static reactor.rabbitmq.ExchangeSpecification.exchange; -public class EventBusConfig { - private String domainEventsExchangeName; +@RequiredArgsConstructor +public class EventBusConfig { - public EventBusConfig(String domainEventsExchangeName) { - this.domainEventsExchangeName = domainEventsExchangeName; - } + private final String domainEventsExchangeName; public DomainEventBus domainEventBus(ReactiveMessageSender sender) { sender.getTopologyCreator().declare(exchange(domainEventsExchangeName).durable(true).type("topic")).subscribe(); diff --git a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java index f12d3eaf..2ee716e6 100644 --- a/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java @@ -2,6 +2,7 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; @@ -22,13 +23,10 @@ import java.util.logging.Level; @Log +@RequiredArgsConstructor public class RabbitMqConfig { - private String appName; - - public RabbitMqConfig(String appName) { - this.appName = appName; - } + private final String appName; public ReactiveMessageSender messageSender(ConnectionFactoryProvider provider, MessageConverter converter, RabbitProperties rabbitProperties) { @@ -63,9 +61,9 @@ public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSuppli Mono createSenderConnectionMono(ConnectionFactory factory, String name) { return Mono.fromCallable(() -> factory.newConnection(name)) - .doOnError(err -> - log.log(Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err) - ) + .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))) .cache(); } 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 index 3abf1e90..0ec7bc89 100644 --- 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 @@ -1,8 +1,8 @@ package org.reactivecommons.async.rabbit; import io.micrometer.core.instrument.MeterRegistry; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; @@ -29,7 +29,7 @@ @Log @Getter -@AllArgsConstructor +@RequiredArgsConstructor public class RabbitMQBrokerProvider implements BrokerProvider { private final String domain; private final AsyncProps props; @@ -143,12 +143,12 @@ public void listenQueries(HandlerResolver resolver) { @Override public void listenReplies() { if (props.isListenReplies()) { - final ApplicationReplyListener replyListener = - new ApplicationReplyListener(router, - receiver, - props.getBrokerConfigProps().getReplyQueue(), - props.getBrokerConfigProps().getGlobalReplyExchangeName(), - props.getCreateTopology()); + final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, + receiver, + props.getBrokerConfigProps().getReplyQueue(), + props.getBrokerConfigProps().getGlobalReplyExchangeName(), + props.getCreateTopology() + ); replyListener.startListening(config.getRoutingKey()); } } 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 index 8c81db82..1dd5fb9f 100644 --- 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 @@ -1,7 +1,7 @@ package org.reactivecommons.async.rabbit; import io.micrometer.core.instrument.MeterRegistry; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.config.BrokerConfig; import org.reactivecommons.async.commons.ext.CustomReporter; @@ -19,7 +19,7 @@ import org.springframework.stereotype.Service; @Service("rabbitmq") -@AllArgsConstructor +@RequiredArgsConstructor public class RabbitMQBrokerProviderFactory implements BrokerProviderFactory { private final BrokerConfig config; private final ReactiveReplyRouter router; 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 index 36615c4e..9db580c3 100644 --- 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 @@ -1,6 +1,6 @@ package org.reactivecommons.async.rabbit; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.config.BrokerConfig; import org.reactivecommons.async.commons.converters.MessageConverter; @@ -13,7 +13,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -@AllArgsConstructor +@RequiredArgsConstructor public class RabbitMQDiscardProvider implements DiscardProvider { private final AsyncProps props; private final BrokerConfig config; 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 index 18be2b17..40142e83 100644 --- 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 @@ -2,8 +2,9 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.SneakyThrows; -import lombok.experimental.UtilityClass; import lombok.extern.java.Log; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.commons.DLQDiscardNotifier; @@ -51,8 +52,8 @@ import java.util.logging.Level; @Log -@UtilityClass -public class RabbitMQSetupUtils { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class RabbitMQSetupUtils { private static final String LISTENER_TYPE = "listener"; private static final String SENDER_TYPE = "sender"; private static final String DEFAULT_PROTOCOL; @@ -180,7 +181,7 @@ private static void setUpSSL(ConnectionFactory factory, RabbitProperties propert logDetails(trustManagers); - if (ssl.getVerifyHostname()) { + if (ssl.isVerifyHostname()) { factory.enableHostnameVerification(); } } 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 b852a0db..44fe7f0a 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,18 +1,16 @@ package org.reactivecommons.async.rabbit.config.props; +import lombok.NoArgsConstructor; import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Map; +@NoArgsConstructor @ConfigurationProperties(prefix = "app.async") public class AsyncRabbitPropsDomainProperties extends GenericAsyncPropsDomainProperties { - - public AsyncRabbitPropsDomainProperties() { - } - public AsyncRabbitPropsDomainProperties(Map m) { super(m); } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java index 735e4e2e..32ffd8c7 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java @@ -68,9 +68,8 @@ private String resolveTemporaryQueue(AtomicReference property, String su final String replyName = generateNameFrom(getAppName(), suffix); if (property.compareAndSet(null, replyName)) { return replyName; - } else { - return property.get(); } + return property.get(); } return name; } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java index 41346510..19d0c341 100644 --- a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java @@ -1,5 +1,7 @@ package org.reactivecommons.async.rabbit.config.spring; +import lombok.Getter; +import lombok.Setter; import org.springframework.boot.convert.DurationUnit; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -14,78 +16,96 @@ public class RabbitPropertiesBase { /** * RabbitMQ host. */ + @Setter + @Getter private String host = "localhost"; /** * RabbitMQ port. */ + @Setter + @Getter private int port = 5672; /** * Login user to authenticate to the communications. */ + @Setter + @Getter private String username = "guest"; /** * Login to authenticate against the communications. */ + @Setter + @Getter private String password = "guest"; //NOSONAR /** * SSL configuration. */ + @Getter private final Ssl ssl = new Ssl(); /** * Virtual host to use when connecting to the communications. */ + @Getter private String virtualHost; /** * Comma-separated list of addresses to which the client should connect. */ + @Getter private String addresses; /** * Requested heartbeat timeout; zero for none. If a duration suffix is not specified, * seconds will be used. */ + @Getter + @Setter @DurationUnit(ChronoUnit.SECONDS) private Duration requestedHeartbeat; /** * Whether to enable publisher confirms. */ + @Setter + @Getter private boolean publisherConfirms; /** * Whether to enable publisher returns. */ + @Setter + @Getter private boolean publisherReturns; /** * Connection timeout. Set it to zero to wait forever. */ + @Getter + @Setter private Duration connectionTimeout; /** * Cache configuration. */ + @Getter private final Cache cache = new Cache(); /** * Listener container configuration. */ + @Getter private final Listener listener = new Listener(); + @Getter private final Template template = new Template(); private List
parsedAddresses; - public String getHost() { - return this.host; - } - /** * Returns the host from the first address, or the configured host if no addresses * have been set. @@ -101,14 +121,6 @@ public String determineHost() { return this.parsedAddresses.get(0).host; } - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return this.port; - } - /** * Returns the port from the first address, or the configured port if no addresses * have been set. @@ -125,14 +137,6 @@ public int determinePort() { return address.port; } - public void setPort(int port) { - this.port = port; - } - - public String getAddresses() { - return this.addresses; - } - /** * Returns the comma-separated addresses or a single address ({@code host:port}) * created from the configured host and port if no addresses have been set. @@ -163,10 +167,6 @@ private List
parseAddresses(String addresses) { return parsedAddressesLocal; } - public String getUsername() { - return this.username; - } - /** * If addresses have been set and the first address has a username it is returned. * Otherwise returns the result of calling {@code getUsername()}. @@ -183,14 +183,6 @@ public String determineUsername() { return (address.username != null) ? address.username : this.username; } - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - /** * If addresses have been set and the first address has a password it is returned. * Otherwise returns the result of calling {@code getPassword()}. @@ -207,18 +199,6 @@ public String determinePassword() { return (address.password != null) ? address.password : getPassword(); } - public void setPassword(String password) { - this.password = password; - } - - public Ssl getSsl() { - return this.ssl; - } - - public String getVirtualHost() { - return this.virtualHost; - } - /** * If addresses have been set and the first address has a virtual host it is returned. * Otherwise returns the result of calling {@code getVirtualHost()}. @@ -239,50 +219,8 @@ public void setVirtualHost(String virtualHost) { this.virtualHost = "".equals(virtualHost) ? "/" : virtualHost; } - public Duration getRequestedHeartbeat() { - return this.requestedHeartbeat; - } - - public void setRequestedHeartbeat(Duration requestedHeartbeat) { - this.requestedHeartbeat = requestedHeartbeat; - } - - public boolean isPublisherConfirms() { - return this.publisherConfirms; - } - - public void setPublisherConfirms(boolean publisherConfirms) { - this.publisherConfirms = publisherConfirms; - } - - public boolean isPublisherReturns() { - return this.publisherReturns; - } - - public void setPublisherReturns(boolean publisherReturns) { - this.publisherReturns = publisherReturns; - } - - public Duration getConnectionTimeout() { - return this.connectionTimeout; - } - - public void setConnectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public Cache getCache() { - return this.cache; - } - - public Listener getListener() { - return this.listener; - } - - public Template getTemplate() { - return this.template; - } - + @Getter + @Setter public static class Ssl { /** @@ -335,102 +273,17 @@ public static class Ssl { */ private boolean verifyHostname = true; - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getKeyStore() { - return this.keyStore; - } - - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStoreType() { - return this.keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStorePassword() { - return this.keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getTrustStore() { - return this.trustStore; - } - - public void setTrustStore(String trustStore) { - this.trustStore = trustStore; - } - - public String getTrustStoreType() { - return this.trustStoreType; - } - - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - public String getTrustStorePassword() { - return this.trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public String getAlgorithm() { - return this.algorithm; - } - - public void setAlgorithm(String sslAlgorithm) { - this.algorithm = sslAlgorithm; - } - - public boolean isValidateServerCertificate() { - return this.validateServerCertificate; - } - - public void setValidateServerCertificate(boolean validateServerCertificate) { - this.validateServerCertificate = validateServerCertificate; - } - - public boolean getVerifyHostname() { - return this.verifyHostname; - } - - public void setVerifyHostname(boolean verifyHostname) { - this.verifyHostname = verifyHostname; - } - } + @Getter public static class Cache { private final Channel channel = new Channel(); private final Connection connection = new Connection(); - public Channel getChannel() { - return this.channel; - } - - public Connection getConnection() { - return this.connection; - } - + @Getter + @Setter public static class Channel { /** @@ -445,24 +298,10 @@ public static class Channel { */ private Duration checkoutTimeout; - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - - public Duration getCheckoutTimeout() { - return this.checkoutTimeout; - } - - public void setCheckoutTimeout(Duration checkoutTimeout) { - this.checkoutTimeout = checkoutTimeout; - } - } + @Getter + @Setter public static class Connection { /** @@ -475,22 +314,6 @@ public static class Connection { */ private Integer size; - public String getMode() { - return this.mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - } } @@ -510,6 +333,9 @@ public enum ContainerType { } + + @Getter + @Setter public static class Listener { /** @@ -521,24 +347,10 @@ public static class Listener { private final DirectContainer direct = new DirectContainer(); - public ContainerType getType() { - return this.type; - } - - public void setType(ContainerType containerType) { - this.type = containerType; - } - - public SimpleContainer getSimple() { - return this.simple; - } - - public DirectContainer getDirect() { - return this.direct; - } - } + @Getter + @Setter public abstract static class AmqpContainer { /** @@ -572,57 +384,16 @@ public abstract static class AmqpContainer { */ private final ListenerRetry retry = new ListenerRetry(); - public boolean isAutoStartup() { - return this.autoStartup; - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public Integer getAcknowledgeMode() { - return this.acknowledgeMode; - } - - public void setAcknowledgeMode(Integer acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; - } - - public Integer getPrefetch() { - return this.prefetch; - } - - public void setPrefetch(Integer prefetch) { - this.prefetch = prefetch; - } - - public Boolean getDefaultRequeueRejected() { - return this.defaultRequeueRejected; - } - - public void setDefaultRequeueRejected(Boolean defaultRequeueRejected) { - this.defaultRequeueRejected = defaultRequeueRejected; - } - - public Duration getIdleEventInterval() { - return this.idleEventInterval; - } - - public void setIdleEventInterval(Duration idleEventInterval) { - this.idleEventInterval = idleEventInterval; - } public abstract boolean isMissingQueuesFatal(); - public ListenerRetry getRetry() { - return this.retry; - } - } /** * Configuration properties for {@code SimpleMessageListenerContainer}. */ + @Getter + @Setter public static class SimpleContainer extends AmqpContainer { /** @@ -648,44 +419,13 @@ public static class SimpleContainer extends AmqpContainer { */ private boolean missingQueuesFatal = true; - public Integer getConcurrency() { - return this.concurrency; - } - - public void setConcurrency(Integer concurrency) { - this.concurrency = concurrency; - } - - public Integer getMaxConcurrency() { - return this.maxConcurrency; - } - - public void setMaxConcurrency(Integer maxConcurrency) { - this.maxConcurrency = maxConcurrency; - } - - public Integer getTransactionSize() { - return this.transactionSize; - } - - public void setTransactionSize(Integer transactionSize) { - this.transactionSize = transactionSize; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - } /** * Configuration properties for {@code DirectMessageListenerContainer}. */ + @Getter + @Setter public static class DirectContainer extends AmqpContainer { /** @@ -699,25 +439,10 @@ public static class DirectContainer extends AmqpContainer { */ private boolean missingQueuesFatal = false; - public Integer getConsumersPerQueue() { - return this.consumersPerQueue; - } - - public void setConsumersPerQueue(Integer consumersPerQueue) { - this.consumersPerQueue = consumersPerQueue; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - } + @Getter + @Setter public static class Template { private final Retry retry = new Retry(); @@ -753,60 +478,10 @@ public static class Template { */ private String queue; - public Retry getRetry() { - return this.retry; - } - - public Boolean getMandatory() { - return this.mandatory; - } - - public void setMandatory(Boolean mandatory) { - this.mandatory = mandatory; - } - - public Duration getReceiveTimeout() { - return this.receiveTimeout; - } - - public void setReceiveTimeout(Duration receiveTimeout) { - this.receiveTimeout = receiveTimeout; - } - - public Duration getReplyTimeout() { - return this.replyTimeout; - } - - public void setReplyTimeout(Duration replyTimeout) { - this.replyTimeout = replyTimeout; - } - - public String getExchange() { - return this.exchange; - } - - public void setExchange(String exchange) { - this.exchange = exchange; - } - - public String getRoutingKey() { - return this.routingKey; - } - - public void setRoutingKey(String routingKey) { - this.routingKey = routingKey; - } - - public String getQueue() { - return this.queue; - } - - public void setQueue(String queue) { - this.queue = queue; - } - } + @Getter + @Setter public static class Retry { /** @@ -834,63 +509,16 @@ public static class Retry { */ private Duration maxInterval = Duration.ofMillis(10000); - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public int getMaxAttempts() { - return this.maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public Duration getInitialInterval() { - return this.initialInterval; - } - - public void setInitialInterval(Duration initialInterval) { - this.initialInterval = initialInterval; - } - - public double getMultiplier() { - return this.multiplier; - } - - public void setMultiplier(double multiplier) { - this.multiplier = multiplier; - } - - public Duration getMaxInterval() { - return this.maxInterval; - } - - public void setMaxInterval(Duration maxInterval) { - this.maxInterval = maxInterval; - } - } + @Getter + @Setter public static class ListenerRetry extends Retry { /** * Whether retries are stateless or stateful. */ private boolean stateless = true; - - public boolean isStateless() { - return this.stateless; - } - - public void setStateless(boolean stateless) { - this.stateless = stateless; - } - } private static final class Address { @@ -960,7 +588,7 @@ private void parseHostAndPort(String input) { this.port = DEFAULT_PORT; } else { this.host = input.substring(0, portIndex); - this.port = Integer.valueOf(input.substring(portIndex + 1)); + this.port = Integer.parseInt(input.substring(portIndex + 1)); } } 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 index 5ea55767..3d12dc97 100644 --- 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 @@ -95,7 +95,8 @@ public void init() { @Test void shouldCreateDomainEventBus() { when(sender.getTopologyCreator()).thenReturn(creator); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); // Act DomainEventBus domainBus = brokerProvider.getDomainBus(); // Assert @@ -106,9 +107,12 @@ void shouldCreateDomainEventBus() { 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(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 @@ -120,7 +124,8 @@ void shouldCreateDirectAsyncGateway() { @Test void shouldListenDomainEvents() { when(listener.getTopologyCreator()).thenReturn(creator); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.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.getReceiver()).thenReturn(receiver); when(listener.getMaxConcurrency()).thenReturn(1); @@ -135,8 +140,10 @@ void shouldListenDomainEvents() { @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(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()); @@ -152,9 +159,12 @@ void shouldListenNotificationEvents() { @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(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()); @@ -167,9 +177,12 @@ void shouldListenCommands() { @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(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()); From 8dbdfeb0cb270460a7ae1bb5f26c057b48a4eb9c Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:25:37 -0500 Subject: [PATCH 40/50] feat(raw-handler): Raw event handler (#136) * feat(rawhandler): Allow raw message handler to listening events --- .../async/api/HandlerRegistry.java | 8 ++++++ .../async/api/handlers/RawEventHandler.java | 6 +++++ .../async/api/HandlerRegistryTest.java | 23 ++++++++++++++++ .../async/commons/communications/Message.java | 4 ++- .../async/kafka/KafkaDomainEventBus.java | 11 ++++++++ .../async/kafka/KafkaMessage.java | 7 ++++- .../json/KafkaJacksonMessageConverter.java | 5 +++- .../listeners/GenericMessageListener.java | 2 +- .../async/kafka/KafkaDomainEventBusTest.java | 15 +++++++++++ .../KafkaJacksonMessageConverterTest.java | 1 + .../async/rabbit/RabbitDomainEventBus.java | 22 ++++++++++++--- .../async/rabbit/RabbitMessage.java | 7 ++++- .../json/RabbitJacksonMessageConverter.java | 7 +++-- .../listeners/ApplicationEventListener.java | 4 +++ .../listeners/GenericMessageListener.java | 2 +- .../async/helpers/TestStubs.java | 2 +- .../rabbit/RabbitDomainEventBusTest.java | 17 ++++++++++++ .../json/JacksonMessageConverterTest.java | 2 ++ .../ApplicationEventListenerTest.java | 27 ++++++++++++++++++- .../ListenerReporterTestSuperClass.java | 14 +++++++--- .../api/domain/DomainEventBus.java | 3 +++ .../api/domain/RawMessage.java | 5 ++++ .../senders/GenericDomainEventBus.java | 20 ++++++++++++-- .../senders/GenericDomainEventBusTest.java | 26 ++++++++++++++++++ 24 files changed, 220 insertions(+), 20 deletions(-) create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java create mode 100644 domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java index f97d2e8c..71d2e8e5 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java @@ -4,12 +4,14 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.api.handlers.CloudCommandHandler; import org.reactivecommons.async.api.handlers.CloudEventHandler; import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; @@ -50,6 +52,12 @@ public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, C return this; } + public HandlerRegistry listenDomainRawEvent(String domain, String eventName, RawEventHandler handler) { + domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) + .add(new RegisteredEventListener<>(eventName, handler, RawMessage.class)); + return this; + } + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) { domainEventListeners.computeIfAbsent(DEFAULT_DOMAIN, ignored -> new CopyOnWriteArrayList<>()) .add(new RegisteredEventListener<>(eventName, handler, eventClass)); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java new file mode 100644 index 00000000..9ed19005 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.RawMessage; + +public interface RawEventHandler extends EventHandler { +} diff --git a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java index af3db020..b0e4ef8d 100644 --- a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java +++ b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java @@ -5,12 +5,14 @@ import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.api.handlers.CloudCommandHandler; import org.reactivecommons.async.api.handlers.CloudEventHandler; import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; @@ -54,6 +56,20 @@ void shouldListenDomainCloudEvent() { .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); } + @Test + void shouldListenDomainRawEvent() { + SomeRawEventHandler eventHandler = new SomeRawEventHandler(); + + registry.listenDomainRawEvent(domain, name, eventHandler); + + assertThat(registry.getDomainEventListeners().get(domain)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, RawMessage.class, eventHandler)).hasSize(1); + } + @Test void shouldListenEvent() { SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); @@ -269,6 +285,13 @@ public Mono handle(CloudEvent message) { } } + private static class SomeRawEventHandler implements RawEventHandler { + @Override + public Mono handle(RawMessage message) { + return null; + } + } + private static class SomeDomainCommandHandler implements DomainCommandHandler { @Override public Mono handle(Command message) { diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java index 42b12b5c..ad60f221 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java @@ -1,5 +1,7 @@ package org.reactivecommons.async.commons.communications; +import org.reactivecommons.api.domain.RawMessage; + import java.util.Map; /** @@ -7,7 +9,7 @@ * * @author Daniel Bustamante Ospina */ -public interface Message { +public interface Message extends RawMessage { byte[] getBody(); 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 b31f4a78..54de85e0 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 @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; import org.reactivestreams.Publisher; @@ -31,4 +32,14 @@ public Publisher emit(CloudEvent event) { public Publisher emit(String domain, CloudEvent event) { throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); } + + @Override + public Publisher emit(RawMessage event) { + return sender.send(event); + } + + @Override + public Publisher emit(String domain, RawMessage event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } } diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java index bafda174..a0bf4eeb 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java @@ -15,6 +15,7 @@ public class KafkaMessage implements Message { private final byte[] body; private final Properties properties; + private final String type; @Data public static class KafkaMessageProperties implements Properties { @@ -30,7 +31,11 @@ public String getContentType() { } public static KafkaMessage fromDelivery(ReceiverRecord receiverRecord) { - return new KafkaMessage(receiverRecord.value(), createMessageProps(receiverRecord)); + return fromDelivery(receiverRecord, null); + } + + public static KafkaMessage fromDelivery(ReceiverRecord receiverRecord, String type) { + return new KafkaMessage(receiverRecord.value(), createMessageProps(receiverRecord), type); } private static Properties createMessageProps(ReceiverRecord receiverRecord) { diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java index d1f79703..035829e5 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java @@ -22,6 +22,9 @@ public KafkaJacksonMessageConverter(ObjectMapper objectMapper) { @Override public Message toMessage(Object object) { + if (object instanceof KafkaMessage) { + return (KafkaMessage) object; + } byte[] bytes; try { String jsonString = this.objectMapper.writeValueAsString(object); @@ -30,7 +33,7 @@ public Message toMessage(Object object) { throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); } KafkaMessageProperties props = buildProperties(object); - return new KafkaMessage(bytes, props); + return new KafkaMessage(bytes, props, null); } private KafkaMessageProperties buildProperties(Object message) { diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java index fc20596a..7c14e631 100644 --- a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java @@ -112,7 +112,7 @@ protected Mono> handle(ReceiverRecord> handler = getExecutor(executorPath); - final Message message = KafkaMessage.fromDelivery(msj); + final Message message = KafkaMessage.fromDelivery(msj, executorPath); Mono flow = Mono.defer(() -> handler.apply(message)) .transform(enrichPostProcess(message)); 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 index 0dc04cdf..f33f582a 100644 --- 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 @@ -7,6 +7,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -21,6 +22,8 @@ class KafkaDomainEventBusTest { @Mock private CloudEvent cloudEvent; @Mock + private RawMessage rawMessage; + @Mock private ReactiveMessageSender sender; @InjectMocks private KafkaDomainEventBus kafkaDomainEventBus; @@ -48,9 +51,21 @@ void shouldEmitCloudEvent() { .verifyComplete(); } + @Test + void shouldEmitRawMessage() { + // Arrange + when(sender.send(rawMessage)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(kafkaDomainEventBus.emit(rawMessage)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + @Test void operationsShouldNotBeAbleForDomains() { assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, domainEvent)); assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, cloudEvent)); + assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, rawMessage)); } } diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java index a07539fa..3219ebeb 100644 --- a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java @@ -42,6 +42,7 @@ void shouldSerializeDomainEvent() { String expectedJson = "{\"name\":\"test\",\"eventId\":\"" + id + "\",\"data\":{\"name\":\"name\",\"age\":1}}"; // Act Message message = converter.toMessage(testEvent); + assertEquals(message, converter.toMessage(message)); // Assert assertEquals("test", message.getProperties().getTopic()); assertEquals(id, message.getProperties().getKey()); 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 d930f22c..b2bb5eda 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 @@ -3,6 +3,7 @@ import io.cloudevents.CloudEvent; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.commons.config.BrokerConfig; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivestreams.Publisher; @@ -12,6 +13,8 @@ public class RabbitDomainEventBus implements DomainEventBus { + private static final String EVENT_SEND_FAILURE = "Event send failure: "; + private static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; private final ReactiveMessageSender sender; private final String exchange; private final boolean persistentEvents; @@ -29,24 +32,35 @@ 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"); + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); } @Override public Publisher emit(CloudEvent cloudEvent) { return sender.sendWithConfirm(cloudEvent, exchange, cloudEvent.getType(), Collections.emptyMap(), persistentEvents) - .onErrorMap(err -> new RuntimeException("Event send failure: " + cloudEvent.getType(), err)); + .onErrorMap(err -> new RuntimeException(EVENT_SEND_FAILURE + cloudEvent.getType(), err)); } @Override public Publisher emit(String domain, CloudEvent event) { - throw new UnsupportedOperationException("Not implemented yet"); + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); } + @Override + public Publisher emit(RawMessage rawEvent) { + return sender.sendWithConfirm(rawEvent, exchange, rawEvent.getType(), + Collections.emptyMap(), persistentEvents) + .onErrorMap(err -> new RuntimeException(EVENT_SEND_FAILURE + rawEvent.getType(), err)); + } + + @Override + public Publisher emit(String domain, RawMessage event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java index f14446d7..cc35549f 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java @@ -11,6 +11,7 @@ public class RabbitMessage implements Message { private final byte[] body; private final Properties properties; + private final String type; @Data public static class RabbitMessageProperties implements Properties { @@ -22,7 +23,11 @@ public static class RabbitMessageProperties implements Properties { } public static RabbitMessage fromDelivery(Delivery delivery) { - return new RabbitMessage(delivery.getBody(), createMessageProps(delivery)); + return fromDelivery(delivery, null); + } + + public static RabbitMessage fromDelivery(Delivery delivery, String executorPath) { + return new RabbitMessage(delivery.getBody(), createMessageProps(delivery), executorPath); } private static Message.Properties createMessageProps(Delivery msj) { diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java index db19e9fb..1cff647e 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java @@ -18,6 +18,9 @@ public RabbitJacksonMessageConverter(ObjectMapper objectMapper) { @Override public Message toMessage(Object object) { + if (object instanceof RabbitMessage) { + return (RabbitMessage) object; + } byte[] bytes; try { String jsonString = this.objectMapper.writeValueAsString(object); @@ -29,10 +32,10 @@ public Message toMessage(Object object) { if (object instanceof CloudEvent) { props.setContentType(APPLICATION_CLOUD_EVENT_JSON); } else { - props.setContentType(CONTENT_TYPE); + props.setContentType(APPLICATION_JSON); } props.setContentEncoding(StandardCharsets.UTF_8.name()); props.setContentLength(bytes.length); - return new RabbitMessage(bytes, props); + return new RabbitMessage(bytes, props, null); } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java index 11cfd5ba..bd10d077 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java @@ -4,6 +4,7 @@ import lombok.extern.java.Log; import org.reactivecommons.async.api.handlers.CloudEventHandler; import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.EventExecutor; @@ -133,6 +134,9 @@ private Function resolveConverter(RegisteredEventListene if (registeredEventListener.getHandler() instanceof CloudEventHandler) { return messageConverter::readCloudEvent; } + if (registeredEventListener.getHandler() instanceof RawEventHandler) { + return message -> message; + } throw new RuntimeException("Unknown handler type"); } } 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 82e32ac1..b5158a95 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 @@ -121,7 +121,7 @@ protected Mono handle(AcknowledgableDelivery msj, Instan try { final String executorPath = getExecutorPath(msj); final Function> handler = getExecutor(executorPath); - final Message message = RabbitMessage.fromDelivery(msj); + final Message message = RabbitMessage.fromDelivery(msj, executorPath); Mono flow = defer(() -> handler.apply(message)) .transform(enrichPostProcess(message)); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java index 280a84e9..35dc28d2 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java @@ -15,6 +15,6 @@ public static Message mockMessage() { properties.getHeaders().put(CORRELATION_ID, "correlation"); properties.getHeaders().put(SERVED_QUERY_ID, "my-query"); return new RabbitMessage("{\"id\":\"id\",\"name\":\"name\",\"date\":\"2020-10-22T17:03:26.062Z\"}".getBytes(), - properties); + properties, null); } } 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 index 38ed6e4c..ed03150d 100644 --- 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 @@ -7,6 +7,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -24,6 +25,8 @@ class RabbitDomainEventBusTest { @Mock private CloudEvent cloudEvent; @Mock + private RawMessage rawMessage; + @Mock private ReactiveMessageSender sender; private RabbitDomainEventBus rabbitDomainEventBus; private final String domain = "domain"; @@ -59,9 +62,23 @@ void shouldEmitCloudEvent() { .verifyComplete(); } + @Test + void shouldEmitRawMessage() { + // Arrange + when(rawMessage.getType()).thenReturn("event"); + when(sender.sendWithConfirm(any(RawMessage.class), anyString(), anyString(), any(), anyBoolean())) + .thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(rabbitDomainEventBus.emit(rawMessage)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + @Test void operationsShouldNotBeAbleForDomains() { assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, domainEvent)); assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, cloudEvent)); + assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, rawMessage)); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java index 15b697b4..1e39a7ad 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java @@ -19,6 +19,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; class JacksonMessageConverterTest { @@ -34,6 +35,7 @@ static void setUp() { @Test void toMessage() { final Message message = converter.toMessage(new SampleClass("42", "Daniel", new Date())); + assertEquals(message, converter.toMessage(message)); assertThat(new String(message.getBody())).contains("42").contains("Daniel"); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java index 763785f7..ff2b79b2 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java @@ -1,5 +1,7 @@ package org.reactivecommons.async.rabbit.listeners; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -8,6 +10,7 @@ import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; +import java.net.URI; import java.util.Optional; import java.util.UUID; @@ -25,6 +28,13 @@ public class ApplicationEventListenerTest extends ListenerReporterTestSuperClass "app.event.test2", UUID.randomUUID().toString(), new DummyMessage() ); + private final CloudEvent cloudEvent = CloudEventBuilder.v1() + .withType("app.event.test") + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("/test")) + .withData("application/json", "{}".getBytes()) + .build(); + @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() @@ -34,6 +44,20 @@ void shouldSendErrorToCustomErrorReporter() throws InterruptedException { assertSendErrorToCustomReporter(registry, createSource(DomainEvent::getName, event1)); } + @Test + void shouldResolveCorrectCloudEventHandler() throws InterruptedException { + final HandlerRegistry registry = HandlerRegistry.register() + .listenCloudEvent("app.event.test", m -> error(new RuntimeException("testEx"))); + assertSendErrorToCustomReporter(registry, createSource(CloudEvent::getType, cloudEvent)); + } + + @Test + void shouldResolveCorrectRawHandler() throws InterruptedException { + final HandlerRegistry registry = HandlerRegistry.register() + .listenDomainRawEvent("domain","app.event.test", m -> error(new RuntimeException("testEx"))); + assertSendErrorToCustomReporter(registry, createSource(CloudEvent::getType, cloudEvent)); + } + @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() @@ -44,7 +68,8 @@ void shouldContinueAfterReportError() throws InterruptedException { m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class ); - assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, event2)); + assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, + event2)); } @Override diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java index 2cfeb669..2e101c38 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java @@ -65,7 +65,7 @@ public abstract class ListenerReporterTestSuperClass { protected final CustomReporter errorReporter = mock(CustomReporter.class); protected final Semaphore semaphore = new Semaphore(0); protected final Semaphore successSemaphore = new Semaphore(0); - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = new DefaultObjectMapperSupplier().get(); private final Receiver receiver = mock(Receiver.class); protected final ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener( receiver, topologyCreator @@ -147,9 +147,15 @@ protected Flux createSource(Function rout protected abstract GenericMessageListener createMessageListener(final HandlerResolver handlerResolver); private HandlerResolver createHandlerResolver(final HandlerRegistry registry) { - final Map> eventHandlers = Stream.concat( - registry.getDynamicEventHandlers().stream(), - registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()) + Stream> listenerStream = Stream.concat( + registry.getDynamicEventHandlers().stream(), + registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()); + if (registry.getDomainEventListeners().containsKey("domain")) { + listenerStream = Stream.concat( + listenerStream, + registry.getDomainEventListeners().get("domain").stream()); + } + final Map> eventHandlers = listenerStream .collect(toMap(RegisteredEventListener::getPath, identity())); final Map> eventsToBind = registry.getDomainEventListeners() .get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); 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 0c62f8a8..da5b7315 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 @@ -11,4 +11,7 @@ public interface DomainEventBus { Publisher emit(CloudEvent event); Publisher emit(String domain, CloudEvent event); + + Publisher emit(RawMessage event); + Publisher emit(String domain, RawMessage event); } diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java new file mode 100644 index 00000000..1dd9fa01 --- /dev/null +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java @@ -0,0 +1,5 @@ +package org.reactivecommons.api.domain; + +public interface RawMessage { + String getType(); +} 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 index 0feda04a..fd7fbe25 100644 --- 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 @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -14,6 +15,7 @@ @RequiredArgsConstructor public class GenericDomainEventBus implements DomainEventBus { + private static final String DOMAIN_NOT_FOUND = "Domain not found: "; private final ConcurrentMap domainEventBuses; @@ -26,7 +28,7 @@ public Publisher emit(DomainEvent event) { 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 Mono.error(() -> new InvalidConfigurationException(DOMAIN_NOT_FOUND + domain)); } return domainEventBus.emit(event); } @@ -40,7 +42,21 @@ public Publisher emit(CloudEvent event) { 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 Mono.error(() -> new InvalidConfigurationException(DOMAIN_NOT_FOUND + domain)); + } + return domainEventBus.emit(event); + } + + @Override + public Publisher emit(RawMessage event) { + return emit(DEFAULT_DOMAIN, event); + } + + @Override + public Publisher emit(String domain, RawMessage 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/senders/GenericDomainEventBusTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java index c2f23fe6..43d62071 100644 --- 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 @@ -8,6 +8,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -28,6 +29,8 @@ class GenericDomainEventBusTest { @Mock private CloudEvent cloudEvent; @Mock + private Message rawMessage; + @Mock private DomainEvent domainEvent; private GenericDomainEventBus genericDomainEventBus; @@ -108,4 +111,27 @@ void shouldFailWhenNoDomainFoundWithCloudEvent() { .expectError(InvalidConfigurationException.class) .verify(); } + + @Test + void shouldEmitRawEventWithSpecificDomain() { + // Arrange + when(domainEventBus2.emit(rawMessage)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(DOMAIN_2, rawMessage)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus2).emit(rawMessage); + } + + @Test + void shouldFailWhenNoDomainFoundEmittingRawEvent() { + // Arrange + // Act + Mono flow = Mono.from(genericDomainEventBus.emit("another", rawMessage)); + // Assert + StepVerifier.create(flow) + .expectError(InvalidConfigurationException.class) + .verify(); + } } From b3e4b16e597e909b507228e44f20bb55011ac7c1 Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Mon, 24 Feb 2025 21:31:45 +0000 Subject: [PATCH 41/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 9 +++++++++ gradle.properties | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72c03baf..10b2881b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [v5.3.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.3.0) (2025-02-24) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.3...v5.3.0) + +**Merged pull requests:** + +- feat\(raw-handler\): Raw event handler [\#136](https://github.com/reactive-commons/reactive-commons-java/pull/136) ([juancgalvis](https://github.com/juancgalvis)) +- refactor: fix code semells [\#134](https://github.com/reactive-commons/reactive-commons-java/pull/134) ([luisgomez29](https://github.com/luisgomez29)) + ## [v5.2.3](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.3) (2025-01-24) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.2...v5.2.3) diff --git a/gradle.properties b/gradle.properties index a8015b09..2dd97512 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.2.3 +version=5.3.0 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 From d6c8ef0d50f82883d68e9e730a89f35224fa2544 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:03:49 -0500 Subject: [PATCH 42/50] chore(deps): update dependencies (#135) --- build.gradle | 4 ++-- main.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 669fdc4a..c064f2a4 100644 --- a/build.gradle +++ b/build.gradle @@ -13,9 +13,9 @@ buildscript { plugins { id 'jacoco' id 'org.sonarqube' version '6.0.1.5171' - id 'org.springframework.boot' version '3.4.2' apply false + id 'org.springframework.boot' version '3.4.3' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.20.13' + id 'co.com.bancolombia.cleanArchitecture' version '3.20.15' } repositories { diff --git a/main.gradle b/main.gradle index 4df770e4..9e613603 100644 --- a/main.gradle +++ b/main.gradle @@ -82,7 +82,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.2' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.3' } } From 595b3aaaefae888877895d677c2f9f47d96ca707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Guillermo=20G=C3=B3mez=20Galeano?= <44306301+luisgomez29@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:28:08 -0500 Subject: [PATCH 43/50] fix(deps): update dependencies (#137) Co-authored-by: lugomez --- build.gradle | 2 +- main.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c064f2a4..0f803763 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath('com.github.ben-manes:gradle-versions-plugin:0.11.1') + classpath('com.github.ben-manes:gradle-versions-plugin:0.52.0') } } diff --git a/main.gradle b/main.gradle index 9e613603..4e8e4e26 100644 --- a/main.gradle +++ b/main.gradle @@ -3,7 +3,7 @@ allprojects { apply plugin: 'jacoco' java { - sourceCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } From 5ef0ad19eac3381c213c51c2ace3380985fcc63f Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Mon, 24 Feb 2025 22:34:39 +0000 Subject: [PATCH 44/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 9 +++++++++ gradle.properties | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b2881b..e98aa847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [v5.3.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.3.1) (2025-02-24) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.3.0...v5.3.1) + +**Merged pull requests:** + +- chore\(deps\): update dependencies [\#137](https://github.com/reactive-commons/reactive-commons-java/pull/137) ([luisgomez29](https://github.com/luisgomez29)) +- fix\(deps\): update dependencies [\#135](https://github.com/reactive-commons/reactive-commons-java/pull/135) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.3.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.3.0) (2025-02-24) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.3...v5.3.0) diff --git a/gradle.properties b/gradle.properties index 2dd97512..fe13053f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.3.0 +version=5.3.1 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 From ac9291a63b16d83ec84e07f35fac4ee5c8793825 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:04:03 -0500 Subject: [PATCH 45/50] build(deps): update dependencies (#138) --- build.gradle | 4 ++-- main.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 0f803763..f36490db 100644 --- a/build.gradle +++ b/build.gradle @@ -12,8 +12,8 @@ buildscript { plugins { id 'jacoco' - id 'org.sonarqube' version '6.0.1.5171' - id 'org.springframework.boot' version '3.4.3' apply false + id 'org.sonarqube' version '6.1.0.5360' + id 'org.springframework.boot' version '3.4.4' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' id 'co.com.bancolombia.cleanArchitecture' version '3.20.15' } diff --git a/main.gradle b/main.gradle index 4e8e4e26..39c14551 100644 --- a/main.gradle +++ b/main.gradle @@ -82,7 +82,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.3' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.4' } } From 51d4c210c567862ccb76c0a31a778beec1d3e6b2 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Thu, 10 Apr 2025 15:53:08 -0500 Subject: [PATCH 46/50] feat(open-multi-domain-api): Allow use any domain as source or destination (#139) * feat(open-multi-domain-api): Allow use any domain as source or destination * feat(open-multi-domain-api): add unit tests --- .../async/api/HandlerRegistry.java | 122 ++++++++++++------ .../async/api/handlers/RawCommandHandler.java | 6 + .../registered/RegisteredDomainHandlers.java | 18 +++ .../async/api/HandlerRegistryTest.java | 66 ++++++++-- .../async/commons/HandlerResolver.java | 15 ++- .../async/commons/HandlerResolverBuilder.java | 121 ----------------- .../resolver/HandlerResolverBuilder.java | 106 +++++++++++++++ .../utils/resolver/HandlerResolverUtil.java | 61 --------- .../async/rabbit/HandlerResolverTest.java | 20 +++ .../ApplicationCommandListenerPerfTest.java | 2 + .../ListenerReporterTestSuperClass.java | 7 +- .../java/sample/SampleRestController.java | 12 +- .../src/main/resources/application.yaml | 2 +- .../ReactiveCommonsListenersConfig.java | 2 +- .../async/rabbit/RabbitMQBrokerProvider.java | 70 +++++----- .../rabbit/RabbitMQBrokerProviderTest.java | 4 +- 16 files changed, 357 insertions(+), 277 deletions(-) create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java create mode 100644 async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java delete mode 100644 async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java create mode 100644 async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java delete mode 100644 async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java index 71d2e8e5..1df771da 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java @@ -11,26 +11,29 @@ import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.RawCommandHandler; import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredDomainHandlers; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import java.lang.reflect.ParameterizedType; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @Getter @NoArgsConstructor(access = AccessLevel.PACKAGE) public final class HandlerRegistry { public static final String DEFAULT_DOMAIN = "app"; - private final Map>> domainEventListeners = new ConcurrentHashMap<>(); - private final List> dynamicEventHandlers = new CopyOnWriteArrayList<>(); - private final List> eventNotificationListener = new CopyOnWriteArrayList<>(); - private final List> handlers = new CopyOnWriteArrayList<>(); - private final List> commandHandlers = new CopyOnWriteArrayList<>(); + private final RegisteredDomainHandlers> domainEventListeners = + new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> dynamicEventHandlers = + new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> eventNotificationListener = + new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> handlers = new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> commandHandlers = + new RegisteredDomainHandlers<>(); public static HandlerRegistry register() { @@ -39,90 +42,136 @@ public static HandlerRegistry register() { return instance; } + //events: DomainEvent + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) { + return listenDomainEvent(DEFAULT_DOMAIN, eventName, handler, eventClass); + } + public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, Class eventClass) { - domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, eventClass)); + domainEventListeners.add(domain, new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } - public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, CloudEventHandler handler) { - domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); - return this; + // events: CloudEvent + public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler handler) { + return listenDomainCloudEvent(DEFAULT_DOMAIN, eventName, handler); } - public HandlerRegistry listenDomainRawEvent(String domain, String eventName, RawEventHandler handler) { - domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, RawMessage.class)); + public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, CloudEventHandler handler) { + domainEventListeners.add(domain, new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); return this; } - public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) { - domainEventListeners.computeIfAbsent(DEFAULT_DOMAIN, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, eventClass)); - return this; + // events: RawMessage + public HandlerRegistry listenRawEvent(String eventName, RawEventHandler handler) { + return listenDomainRawEvent(DEFAULT_DOMAIN, eventName, handler); } - public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler handler) { - domainEventListeners.computeIfAbsent(DEFAULT_DOMAIN, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); + public HandlerRegistry listenDomainRawEvent(String domain, String eventName, RawEventHandler handler) { + domainEventListeners.add(domain, new RegisteredEventListener<>(eventName, handler, RawMessage.class)); return this; } + // notifications: DomainEvent public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, Class eventClass) { - eventNotificationListener.add(new RegisteredEventListener<>(eventName, handler, eventClass)); + return listenNotificationEvent(DEFAULT_DOMAIN, eventName, handler, eventClass); + } + + public HandlerRegistry listenNotificationEvent(String domain, String eventName, DomainEventHandler handler, + Class eventClass) { + eventNotificationListener.add(domain, new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } + // notifications: CloudEvent public HandlerRegistry listenNotificationCloudEvent(String eventName, CloudEventHandler handler) { - eventNotificationListener.add(new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); + return listenNotificationCloudEvent(DEFAULT_DOMAIN, eventName, handler); + } + + public HandlerRegistry listenNotificationCloudEvent(String domain, String eventName, CloudEventHandler handler) { + eventNotificationListener.add(domain, new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); + return this; + } + + // notifications: RawMessage + public HandlerRegistry listenNotificationRawEvent(String eventName, RawEventHandler handler) { + return listenNotificationRawEvent(DEFAULT_DOMAIN, eventName, handler); + } + + public HandlerRegistry listenNotificationRawEvent(String domain, String eventName, RawEventHandler handler) { + eventNotificationListener.add(domain, new RegisteredEventListener<>(eventName, handler, RawMessage.class)); return this; } + // dynamic: DomainEvent supported only for default domain public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, Class eventClass) { - dynamicEventHandlers.add(new RegisteredEventListener<>(eventNamePattern, handler, eventClass)); + dynamicEventHandlers.add(DEFAULT_DOMAIN, new RegisteredEventListener<>(eventNamePattern, handler, eventClass)); return this; } + // dynamic: CloudEvent supported only for default domain public HandlerRegistry handleDynamicCloudEvents(String eventNamePattern, CloudEventHandler handler) { - dynamicEventHandlers.add(new RegisteredEventListener<>(eventNamePattern, handler, CloudEvent.class)); + dynamicEventHandlers.add(DEFAULT_DOMAIN, new RegisteredEventListener<>(eventNamePattern, handler, + CloudEvent.class)); return this; } + // commands: Command public HandlerRegistry handleCommand(String commandName, DomainCommandHandler fn, Class commandClass) { - commandHandlers.add(new RegisteredCommandHandler<>(commandName, fn, commandClass)); + return handleCommand(DEFAULT_DOMAIN, commandName, fn, commandClass); + } + + public HandlerRegistry handleCommand(String domain, String commandName, DomainCommandHandler fn, + Class commandClass) { + commandHandlers.add(domain, new RegisteredCommandHandler<>(commandName, fn, commandClass)); return this; } + // commands: CloudEvent public HandlerRegistry handleCloudEventCommand(String commandName, CloudCommandHandler handler) { - commandHandlers.add(new RegisteredCommandHandler<>(commandName, handler, CloudEvent.class)); + return handleCloudEventCommand(DEFAULT_DOMAIN, commandName, handler); + } + + public HandlerRegistry handleCloudEventCommand(String domain, String commandName, CloudCommandHandler handler) { + commandHandlers.add(domain, new RegisteredCommandHandler<>(commandName, handler, CloudEvent.class)); + return this; + } + + // commands: RawMessage + public HandlerRegistry handleRawCommand(String commandName, RawCommandHandler handler) { + return handleRawCommand(DEFAULT_DOMAIN, commandName, handler); + } + + public HandlerRegistry handleRawCommand(String domain, String commandName, RawCommandHandler handler) { + commandHandlers.add(domain, new RegisteredCommandHandler<>(commandName, handler, RawMessage.class)); return this; } + // queries: Query public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) { - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass )); return this; } public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) { - handlers.add(new RegisteredQueryHandler<>(resource, handler, queryClass)); + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, handler, queryClass)); return this; } public HandlerRegistry serveCloudEventQuery(String resource, QueryHandler handler) { - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), CloudEvent.class )); return this; } - public HandlerRegistry serveCloudEventQuery(String resource, QueryHandlerDelegate handler) { - handlers.add(new RegisteredQueryHandler<>(resource, handler, CloudEvent.class)); + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandlerDelegate handler) { + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, handler, CloudEvent.class)); return this; } @@ -139,7 +188,8 @@ public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEv @Deprecated(forRemoval = true) public HandlerRegistry handleCommand(String commandName, DomainCommandHandler handler) { - commandHandlers.add(new RegisteredCommandHandler<>(commandName, handler, inferGenericParameterType(handler))); + commandHandlers.add(DEFAULT_DOMAIN, new RegisteredCommandHandler<>(commandName, handler, + inferGenericParameterType(handler))); return this; } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java new file mode 100644 index 00000000..c2c63bad --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.RawMessage; + +public interface RawCommandHandler extends CommandHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java new file mode 100644 index 00000000..1410e005 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java @@ -0,0 +1,18 @@ +package org.reactivecommons.async.api.handlers.registered; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class RegisteredDomainHandlers extends ConcurrentHashMap> { + private static final String DEFAULT_DOMAIN = "app"; + + public RegisteredDomainHandlers() { + super(); + put(DEFAULT_DOMAIN, new CopyOnWriteArrayList<>()); + } + + public void add(String domain, T handler) { + computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()).add(handler); + } +} diff --git a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java index b0e4ef8d..cb46b2ef 100644 --- a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java +++ b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java @@ -12,6 +12,7 @@ import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.RawCommandHandler; import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; @@ -25,6 +26,8 @@ class HandlerRegistryTest { private final HandlerRegistry registry = HandlerRegistry.register(); private final String name = "some.event"; + private final String nameRaw = "some.raw.event"; + private final String nameRawNotification = "some.raw.notification.event"; private final String domain = "some-domain"; @@ -60,9 +63,9 @@ void shouldListenDomainCloudEvent() { void shouldListenDomainRawEvent() { SomeRawEventHandler eventHandler = new SomeRawEventHandler(); - registry.listenDomainRawEvent(domain, name, eventHandler); + registry.listenRawEvent(name, eventHandler); - assertThat(registry.getDomainEventListeners().get(domain)) + assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler @@ -120,17 +123,31 @@ void shouldRegisterPatternEventHandler() { @Test void shouldRegisterNotificationEventListener() { registry.listenNotificationEvent(name, message -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getEventNotificationListener()) + assertThat(registry.getEventNotificationListener().get(DEFAULT_DOMAIN)) .anySatisfy(listener -> assertThat(listener.getPath()).isEqualTo(name)); } @Test void shouldRegisterNotificationCloudEventListener() { registry.listenNotificationCloudEvent(name, message -> Mono.empty()); - assertThat(registry.getEventNotificationListener()) + assertThat(registry.getEventNotificationListener().get(DEFAULT_DOMAIN)) .anySatisfy(listener -> assertThat(listener.getPath()).isEqualTo(name)); } + @Test + void shouldRegisterNotificationRawEventListener() { + SomeRawEventHandler eventHandler = new SomeRawEventHandler(); + + registry.listenNotificationRawEvent(nameRawNotification, eventHandler); + + assertThat(registry.getEventNotificationListener().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(nameRawNotification, RawMessage.class, eventHandler)).hasSize(1); + } + @Test @SuppressWarnings("unchecked") void listenEvent() { @@ -151,7 +168,7 @@ void shouldListenDynamicEvent() { registry.handleDynamicEvents(name, eventHandler, SomeDataClass.class); - assertThat(registry.getDynamicEventHandlers()) + assertThat(registry.getDynamicEventHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler @@ -165,7 +182,7 @@ void shouldListenDynamicCloudEvent() { registry.handleDynamicCloudEvents(name, eventHandler); - assertThat(registry.getDynamicEventHandlers()) + assertThat(registry.getDynamicEventHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler @@ -179,7 +196,7 @@ void handleDomainCommand() { registry.handleCommand(name, handler, SomeDataClass.class); - assertThat(registry.getCommandHandlers()) + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, RegisteredCommandHandler::getHandler @@ -193,7 +210,7 @@ void handleCloudEventCommand() { registry.handleCloudEventCommand(name, cloudCommandHandler); - assertThat(registry.getCommandHandlers()) + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, RegisteredCommandHandler::getHandler @@ -201,13 +218,27 @@ void handleCloudEventCommand() { .containsExactly(name, CloudEvent.class, cloudCommandHandler)).hasSize(1); } + @Test + void handleRawCommand() { + SomeRawCommandEventHandler eventHandler = new SomeRawCommandEventHandler(); + + registry.handleRawCommand(nameRaw, eventHandler); + + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, + RegisteredCommandHandler::getHandler + ) + .containsExactly(nameRaw, RawMessage.class, eventHandler)).hasSize(1); + } + @Test void shouldServerCloudEventQuery() { SomeCloudEventQueryHandler queryHandler = new SomeCloudEventQueryHandler(); registry.serveCloudEventQuery(name, queryHandler); - assertThat(registry.getHandlers()) + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, CloudEvent.class)).hasSize(1); @@ -217,7 +248,7 @@ void shouldServerCloudEventQuery() { void handleCommandWithLambda() { registry.handleCommand(name, (Command message) -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getCommandHandlers()) + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass) .containsExactly(name, SomeDataClass.class)).hasSize(1); @@ -227,7 +258,7 @@ void handleCommandWithLambda() { @Test void serveQueryWithLambda() { registry.serveQuery(name, message -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getHandlers()) + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class)).hasSize(1); @@ -237,7 +268,7 @@ void serveQueryWithLambda() { void serveQueryWithTypeInference() { QueryHandler handler = new SomeQueryHandler(); registry.serveQuery(name, handler, SomeDataClass.class); - assertThat(registry.getHandlers()).anySatisfy(registered -> { + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); assertThat(registered).extracting(RegisteredQueryHandler::getHandler) @@ -249,7 +280,7 @@ void serveQueryWithTypeInference() { void serveQueryDelegate() { QueryHandlerDelegate handler = new SomeQueryHandlerDelegate(); registry.serveQuery(name, handler, SomeDataClass.class); - assertThat(registry.getHandlers()).anySatisfy(registered -> { + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); }).hasSize(1); @@ -258,7 +289,7 @@ void serveQueryDelegate() { @Test void serveQueryDelegateWithLambda() { registry.serveQuery(name, (from, message) -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getHandlers()).anySatisfy(registered -> { + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); }).hasSize(1); @@ -306,6 +337,13 @@ public Mono handle(CloudEvent message) { } } + private static class SomeRawCommandEventHandler implements RawCommandHandler { + @Override + public Mono handle(RawMessage message) { + return Mono.empty(); + } + } + private static class SomeQueryHandler implements QueryHandler { @Override public Mono handle(SomeDataClass message) { diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java index 562461ac..ece82925 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java @@ -25,6 +25,18 @@ public class HandlerResolver { private final Matcher matcher = new KeyMatcher(); + public boolean hasNotificationListeners() { + return !eventNotificationListeners.isEmpty(); + } + + public boolean hasCommandHandlers() { + return !commandHandlers.isEmpty(); + } + + public boolean hasQueryHandlers() { + return !queryHandlers.isEmpty(); + } + @SuppressWarnings("unchecked") public RegisteredQueryHandler getQueryHandler(String path) { return (RegisteredQueryHandler) queryHandlers @@ -74,7 +86,8 @@ public void addEventListener(RegisteredEventListener listener) { public void addQueryHandler(RegisteredQueryHandler handler) { if (handler.getPath().contains("*") || handler.getPath().contains("#")) { - throw new RuntimeException("avoid * or # in dynamic handlers, make sure you have no conflicts with cached patterns"); + throw new RuntimeException("avoid * or # in dynamic handlers, make sure you have no conflicts with cached" + + " patterns"); } queryHandlers.put(handler.getPath(), handler); } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java deleted file mode 100644 index cceb6625..00000000 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolverBuilder.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.reactivecommons.async.commons; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; -import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Stream; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Log -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class HandlerResolverBuilder { - - public static HandlerResolver buildResolver(String domain, Map registries, - final DefaultCommandHandler defaultCommandHandler) { - - if (DEFAULT_DOMAIN.equals(domain)) { - final ConcurrentMap> queryHandlers = registries - .values().stream() - .flatMap(r -> r.getHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) - -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll - ); - - final ConcurrentMap> commandHandlers = registries - .values().stream() - .flatMap(r -> r.getCommandHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) - -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll - ); - - final ConcurrentMap> eventNotificationListener = registries - .values() - .stream() - .flatMap(r -> r.getEventNotificationListener().stream()) - .collect(ConcurrentHashMap::new, (map, handler) - -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll - ); - - final ConcurrentMap> eventsToBind = getEventsToBind(domain, - registries); - - final ConcurrentMap> eventHandlers = - getEventHandlersWithDynamics(domain, registries); - - return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, - commandHandlers) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, - Object.class); - } - }; - } - - - final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); - final ConcurrentMap> eventHandlers = - getEventHandlersWithDynamics(domain, registries); - - return new HandlerResolver(new ConcurrentHashMap<>(), eventHandlers, eventsToBind, new ConcurrentHashMap<>(), - new ConcurrentHashMap<>()) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, - Object.class); - } - }; - } - - private static ConcurrentMap> getEventHandlersWithDynamics( - String domain, Map registries) { - // event handlers and dynamic handlers - return registries - .values().stream() - .flatMap(r -> { - if (r.getDomainEventListeners().containsKey(domain)) { - return Stream.concat(r.getDomainEventListeners().get(domain).stream(), getDynamics(domain, r)); - } - return Stream.empty(); - }) - .collect(ConcurrentHashMap::new, (map, handler) - -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll - ); - } - - private static Stream> getDynamics(String domain, HandlerRegistry r) { - if (DEFAULT_DOMAIN.equals(domain)) { - return r.getDynamicEventHandlers().stream(); - } - return Stream.of(); - } - - private static ConcurrentMap> getEventsToBind( - String domain, Map registries) { - return registries - .values().stream() - .flatMap(r -> { - if (r.getDomainEventListeners().containsKey(domain)) { - return r.getDomainEventListeners().get(domain).stream(); - } - return Stream.empty(); - }) - .collect(ConcurrentHashMap::new, (map, handler) - -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll - ); - } -} diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java new file mode 100644 index 00000000..5054362d --- /dev/null +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java @@ -0,0 +1,106 @@ +package org.reactivecommons.async.commons.utils.resolver; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DefaultCommandHandler; +import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Stream; + +@Log +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class HandlerResolverBuilder { + + public static HandlerResolver buildResolver(String domain, Map registries, + final DefaultCommandHandler defaultCommandHandler) { + final ConcurrentMap> queryHandlers = registries + .values() + .stream() + .flatMap(r -> r.getHandlers() + .getOrDefault(domain, List.of()) + .stream()) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + + final ConcurrentMap> commandHandlers = registries + .values() + .stream() + .flatMap(r -> r.getCommandHandlers() + .getOrDefault(domain, List.of()) + .stream()) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + + final ConcurrentMap> eventNotificationListener = registries + .values() + .stream() + .flatMap(r -> r.getEventNotificationListener() + .getOrDefault(domain, List.of()) + .stream()) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + + final ConcurrentMap> eventsToBind = getEventsToBind(domain, + registries); + + final ConcurrentMap> eventHandlers = + getEventHandlersWithDynamics(domain, registries); + + return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, + commandHandlers) { + @Override + @SuppressWarnings("unchecked") + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = super.getCommandHandler(path); + return handler != null ? + handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); + } + }; + } + + private static ConcurrentMap> getEventHandlersWithDynamics( + String domain, Map registries) { + // event handlers and dynamic handlers + return registries + .values() + .stream() + .flatMap(r -> Stream.concat(r.getDomainEventListeners() + .getOrDefault(domain, List.of()) + .stream(), + getDynamics(domain, r))) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + } + + private static Stream> getDynamics(String domain, HandlerRegistry r) { + return r.getDynamicEventHandlers().getOrDefault(domain, List.of()).stream(); + } + + private static ConcurrentMap> getEventsToBind( + String domain, Map registries) { + return registries + .values().stream() + .flatMap(r -> { + if (r.getDomainEventListeners().containsKey(domain)) { + return r.getDomainEventListeners().get(domain).stream(); + } + return Stream.empty(); + }) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + } +} diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java deleted file mode 100644 index 39a4e876..00000000 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverUtil.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.reactivecommons.async.commons.utils.resolver; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; -import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; -import org.reactivecommons.async.commons.HandlerResolver; - -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Stream; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public final class HandlerResolverUtil { - - public static HandlerResolver fromHandlerRegistries(Collection registries, - CommandHandler defaultHandler) { - final ConcurrentMap> queryHandlers = registries.stream() - .flatMap(r -> r.getHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventsToBind = registries.stream() - .flatMap(r -> r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - // event handlers and dynamic handlers - final ConcurrentMap> eventHandlers = registries.stream() - .flatMap(r -> Stream.concat(r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream(), - r.getDynamicEventHandlers().stream())) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> commandHandlers = registries.stream() - .flatMap(r -> r.getCommandHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventNotificationListener = registries.stream() - .flatMap(r -> r.getEventNotificationListener().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, - commandHandlers) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultHandler, Object.class); - } - }; - } -} diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java index 1f6137ae..f7e0d099 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java @@ -59,6 +59,17 @@ void shouldMatchForAWildcardEvent() { Assertions.assertThat(eventListener.getPath()).isEqualTo("some.*"); } + @Test + void shouldThrowError() { + // Arrange + RegisteredQueryHandler handler = new RegisteredQueryHandler<>("*", + (from, message) -> Mono.empty(), + String.class); + // Act + // Assert + Assertions.assertThatThrownBy(() -> resolver.addQueryHandler(handler)).isInstanceOf(RuntimeException.class); + } + @Test void shouldMatchForAnExactEvent() { // Act @@ -67,4 +78,13 @@ void shouldMatchForAnExactEvent() { Assertions.assertThat(eventListener.getPath()).isEqualTo("event.name"); } + @Test + void shouldCheckIfHasListenerTypes() { + // Act + // Assert + Assertions.assertThat(resolver.hasCommandHandlers()).isFalse(); + Assertions.assertThat(resolver.hasQueryHandlers()).isFalse(); + Assertions.assertThat(resolver.hasNotificationListeners()).isFalse(); + } + } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java index 0b12f4fc..3b63f13b 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java @@ -44,6 +44,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; import static reactor.core.publisher.Flux.range; @ExtendWith(MockitoExtension.class) @@ -277,6 +278,7 @@ private HandlerResolver createHandlerResolver(final HandlerRegistry initialRegis ) .block(); final ConcurrentMap> commandHandlers = registry.getCommandHandlers() + .get(DEFAULT_DOMAIN) .stream() .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java index 2e101c38..da384558 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java @@ -148,7 +148,7 @@ protected Flux createSource(Function rout private HandlerResolver createHandlerResolver(final HandlerRegistry registry) { Stream> listenerStream = Stream.concat( - registry.getDynamicEventHandlers().stream(), + registry.getDynamicEventHandlers().get(DEFAULT_DOMAIN).stream(), registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()); if (registry.getDomainEventListeners().containsKey("domain")) { listenerStream = Stream.concat( @@ -160,10 +160,13 @@ private HandlerResolver createHandlerResolver(final HandlerRegistry registry) { final Map> eventsToBind = registry.getDomainEventListeners() .get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); final Map> notificationHandlers = registry.getEventNotificationListener() + .get(DEFAULT_DOMAIN) .stream().collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> queryHandlers = registry.getHandlers().stream() + final Map> queryHandlers = registry.getHandlers().get(DEFAULT_DOMAIN) + .stream() .collect(toMap(RegisteredQueryHandler::getPath, identity())); final Map> commandHandlers = registry.getCommandHandlers() + .get(DEFAULT_DOMAIN) .stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); return new HandlerResolver( new ConcurrentHashMap<>(queryHandlers), diff --git a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java index e42ff4eb..1e26740f 100644 --- a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java +++ b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java @@ -37,16 +37,16 @@ public class SampleRestController { // Query @GetMapping(path = "/api/teams", produces = APPLICATION_JSON_VALUE) - public Mono getTeams() { - CloudEvent query = CloudEventBuilder.v1() + public Mono getTeams() { + RemovedMemberEvent eventData = RemovedMemberEvent.builder().teamName("team").username("member").build(); + CloudEvent event = CloudEventBuilder.v1() .withId(UUID.randomUUID().toString()) .withSource(URI.create("https://reactive-commons.org/foos")) - .withType(Constants.GET_TEAMS) + .withType(Constants.MEMBER_REMOVED_EXTERNAL_DOMAIN) .withTime(OffsetDateTime.now()) - .withData("application/json", CloudEventBuilderExt.asBytes("")) + .withData("application/json", CloudEventBuilderExt.asBytes(eventData)) .build(); - return directAsyncGateway.requestReply(query, target, CloudEvent.class, externalDomain) - .map(event -> CloudEventBuilderExt.fromCloudEventData(event, Teams.class)); + return Mono.from(domainEventBus.emit(event)); } // Query diff --git a/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml b/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml index 878800fa..5b0a5e0a 100644 --- a/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml +++ b/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml @@ -17,7 +17,7 @@ app: async: app: # domain-a connectionProperties: - virtualHost: domain-a + virtualHost: / teams: connectionProperties: virtualHost: / diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java index ba08c712..0f7a7a11 100644 --- a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java @@ -6,7 +6,7 @@ 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.utils.resolver.HandlerResolverBuilder; import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; 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 index 0ec7bc89..a5c626fe 100644 --- 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 @@ -84,7 +84,7 @@ public void listenDomainEvents(HandlerResolver resolver) { @Override public void listenNotificationEvents(HandlerResolver resolver) { - if (!resolver.getNotificationListeners().isEmpty()) { + if (resolver.hasNotificationListeners()) { final ApplicationNotificationListener listener = new ApplicationNotificationListener( receiver, props.getBrokerConfigProps().getDomainEventsExchangeName(), @@ -100,44 +100,48 @@ public void listenNotificationEvents(HandlerResolver resolver) { @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); + if (resolver.hasCommandHandlers()) { + 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(); + 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); + if (resolver.hasQueryHandlers()) { + 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(); + listener.startListener(); + } } @Override 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 index 3d12dc97..eecccfd0 100644 --- 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 @@ -139,6 +139,7 @@ void shouldListenDomainEvents() { @Test @SuppressWarnings({"rawtypes", "unchecked"}) void shouldListenNotificationEvents() { + when(handlerResolver.hasNotificationListeners()).thenReturn(true); when(listener.getTopologyCreator()).thenReturn(creator); when(creator.declare(any(ExchangeSpecification.class))) .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); @@ -148,7 +149,6 @@ void shouldListenNotificationEvents() { 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); @@ -158,6 +158,7 @@ void shouldListenNotificationEvents() { @Test void shouldListenCommands() { + when(handlerResolver.hasCommandHandlers()).thenReturn(true); when(listener.getTopologyCreator()).thenReturn(creator); when(creator.declare(any(ExchangeSpecification.class))) .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); @@ -176,6 +177,7 @@ void shouldListenCommands() { @Test void shouldListenQueries() { + when(handlerResolver.hasQueryHandlers()).thenReturn(true); when(listener.getTopologyCreator()).thenReturn(creator); when(creator.declare(any(ExchangeSpecification.class))) .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); From 17c79b99846e6ccb0233eddae2a65add547ffdfb Mon Sep 17 00:00:00 2001 From: juancgalvis Date: Thu, 10 Apr 2025 21:00:05 +0000 Subject: [PATCH 47/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 9 +++++++++ gradle.properties | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e98aa847..c7a3e89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [v5.4.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.4.0) (2025-04-10) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.3.1...v5.4.0) + +**Merged pull requests:** + +- feat\(open-multi-domain-api\): Allow use any domain as source or destination [\#139](https://github.com/reactive-commons/reactive-commons-java/pull/139) ([juancgalvis](https://github.com/juancgalvis)) +- fix\(deps\): update dependencies [\#138](https://github.com/reactive-commons/reactive-commons-java/pull/138) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.3.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.3.1) (2025-02-24) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.3.0...v5.3.1) diff --git a/gradle.properties b/gradle.properties index fe13053f..1360761c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.3.1 +version=5.4.0 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 From c6a75d55edef7cb078a6831e3d782f2f3d8640c8 Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:58:45 -0500 Subject: [PATCH 48/50] fix(discard): Allow easy discard notifier overriding (#144) --- .../async/commons/DiscardNotifier.java | 2 -- .../rabbit/RabbitMQBrokerProviderFactory.java | 4 +++- .../discard/RabbitMQDiscardProviderConfig.java | 15 +++++++++++++++ .../discard/RabbitMQDiscardProviderFactory.java | 10 ++++++++++ .../RabbitMQDiscardProviderImpl.java} | 5 +++-- .../impl/common/rabbit/RabbitMQConfig.java | 3 ++- .../rabbit/RabbitMQBrokerProviderFactoryTest.java | 7 +++++-- ....java => RabbitMQDiscardProviderImplTest.java} | 5 +++-- 8 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderConfig.java create mode 100644 starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderFactory.java rename starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/{RabbitMQDiscardProvider.java => discard/RabbitMQDiscardProviderImpl.java} (89%) rename starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/{RabbitMQDiscardProviderTest.java => RabbitMQDiscardProviderImplTest.java} (85%) diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java index 82e638b6..f698809b 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java @@ -4,7 +4,5 @@ import reactor.core.publisher.Mono; public interface DiscardNotifier { - Mono notifyDiscard(Message message); - } 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 index 1dd5fb9f..59a2fa3b 100644 --- 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 @@ -12,6 +12,7 @@ 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.discard.RabbitMQDiscardProviderFactory; import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; import org.reactivecommons.async.starter.broker.BrokerProvider; import org.reactivecommons.async.starter.broker.BrokerProviderFactory; @@ -26,6 +27,7 @@ public class RabbitMQBrokerProviderFactory implements BrokerProviderFactory Date: Mon, 9 Jun 2025 16:05:15 +0000 Subject: [PATCH 49/50] Automatic docs and changelog generation [skip ci] --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a3e89a..8047f4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v5.4.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.4.1) (2025-06-09) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.4.0...v5.4.1) + +**Merged pull requests:** + +- fix\(discard\): Allow easy discard notifier overriding [\#144](https://github.com/reactive-commons/reactive-commons-java/pull/144) ([juancgalvis](https://github.com/juancgalvis)) + ## [v5.4.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.4.0) (2025-04-10) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.3.1...v5.4.0) diff --git a/gradle.properties b/gradle.properties index 1360761c..163a961c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=5.4.0 +version=5.4.1 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 From 399449d1cae5abde2b67314a93cf156b02909d1a Mon Sep 17 00:00:00 2001 From: JuanC Galvis <8420868+juancgalvis@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:16:29 -0500 Subject: [PATCH 50/50] docs: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c75ae2f9..b0442e35 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # reactive-commons-java The purpose of reactive-commons is to provide a set of abstractions and implementations over different patterns and practices that make the foundation of a reactive microservices architecture. -Docs: https://reactivecommons.org/reactive-commons-java/ +Docs: [https://bancolombia.github.io/reactive-commons-java/](https://bancolombia.github.io/reactive-commons-java) Other projects: https://github.com/bancolombia