From dc8567caec0dbdb7b1d03e1f4ef4eb7213db576b Mon Sep 17 00:00:00 2001 From: Vasil Ilchev Date: Mon, 23 Mar 2026 09:59:04 +0200 Subject: [PATCH] DMF RabbitMQ Auto declare option (#2960) * DMF RabbitMQ Auto declare option * Fix missing needed amqproperties. Moved AmqpDeadletterProperties as well, not used anymore in main config Separate amqp declaration configuration. --- .../amqp/DmfAmqpDeclarationConfiguration.java | 150 ++++++++++++++++++ .../hawkbit/amqp/DmfApiConfiguration.java | 118 +------------- 2 files changed, 155 insertions(+), 113 deletions(-) create mode 100644 hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfAmqpDeclarationConfiguration.java diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfAmqpDeclarationConfiguration.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfAmqpDeclarationConfiguration.java new file mode 100644 index 000000000..26243d1b4 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfAmqpDeclarationConfiguration.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.amqp; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Auto-declaration of DMF AMQP infrastructure (queues, exchanges, bindings and {@link RabbitAdmin}). + *

+ * Imported by {@link DmfApiConfiguration} — not a {@code @Configuration} class, so it is not + * discoverable by component scanning independently. The {@code hawkbit.dmf.enabled} guard + * is inherited from the importing configuration. + *

+ * Set {@code hawkbit.dmf.rabbitmq.auto-declare=false} to disable auto-declaration. + */ +@ConditionalOnProperty(prefix = "hawkbit.dmf.rabbitmq", name = "auto-declare", matchIfMissing = true) +@EnableConfigurationProperties({ AmqpProperties.class, AmqpDeadletterProperties.class }) +public class DmfAmqpDeclarationConfiguration { + + private final AmqpProperties amqpProperties; + private final AmqpDeadletterProperties amqpDeadletterProperties; + private final ConnectionFactory rabbitConnectionFactory; + + DmfAmqpDeclarationConfiguration( + final AmqpProperties amqpProperties, + final AmqpDeadletterProperties amqpDeadletterProperties, + final ConnectionFactory rabbitConnectionFactory) { + this.amqpProperties = amqpProperties; + this.amqpDeadletterProperties = amqpDeadletterProperties; + this.rabbitConnectionFactory = rabbitConnectionFactory; + } + + /** + * Create a {@link RabbitAdmin} and ignore declaration exceptions. + * {@link RabbitAdmin#setIgnoreDeclarationExceptions(boolean)} + * + * @return the bean + */ + @Bean + public RabbitAdmin rabbitAdmin() { + final RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitConnectionFactory); + rabbitAdmin.setIgnoreDeclarationExceptions(true); + return rabbitAdmin; + } + + /** + * Create the DMF API receiver queue for retrieving DMF messages. + * + * @return the receiver queue + */ + @Bean + public Queue dmfReceiverQueue() { + return new Queue( + amqpProperties.getReceiverQueue(), + true, false, false, + amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange())); + } + + /** + * Create the DMF API receiver queue for authentication requests called by 3rd + * party artifact storages for download authorization by devices. + * + * @return the receiver queue + */ + @Bean + public Queue authenticationReceiverQueue() { + return QueueBuilder.nonDurable(amqpProperties.getAuthenticationReceiverQueue()) + .autoDelete() + .withArguments(getTTLMaxArgsAuthenticationQueue()) + .build(); + } + + /** + * Create DMF exchange. + * + * @return the fanout exchange + */ + @Bean + public FanoutExchange dmfSenderExchange() { + return new FanoutExchange(AmqpSettings.DMF_EXCHANGE); + } + + /** + * Create the Binding dmfReceiverQueue to dmfSenderExchange. + * + * @return the binding and create the queue and exchange + */ + @Bean + public Binding bindDmfSenderExchangeToDmfQueue(final Queue dmfReceiverQueue, final FanoutExchange dmfSenderExchange) { + return BindingBuilder.bind(dmfReceiverQueue).to(dmfSenderExchange); + } + + /** + * Create dead letter queue. + * + * @return the queue + */ + @Bean + public Queue deadLetterQueue() { + return amqpDeadletterProperties.createDeadletterQueue(amqpProperties.getDeadLetterQueue()); + } + + /** + * Create the dead letter fanout exchange. + * + * @return the fanout exchange + */ + @Bean + public FanoutExchange deadLetterExchange() { + return new FanoutExchange(amqpProperties.getDeadLetterExchange()); + } + + /** + * Create the Binding deadLetterQueue to deadLetterExchange. + * + * @return the binding + */ + @Bean + public Binding bindDeadLetterQueueToDeadLetterExchange(final Queue deadLetterQueue, final FanoutExchange deadLetterExchange) { + return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange); + } + + private static Map getTTLMaxArgsAuthenticationQueue() { + final Map args = new HashMap<>(2); + args.put("x-message-ttl", Duration.ofSeconds(30).toMillis()); + args.put("x-max-length", 1_000); + return args; + } +} diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java index d5138fd04..57a52949e 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java @@ -10,34 +10,23 @@ package org.eclipse.hawkbit.amqp; import java.sql.SQLException; -import java.time.Duration; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.artifact.urlresolver.ArtifactUrlResolver; -import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; import org.eclipse.hawkbit.repository.ConfirmationManagement; import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; -import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.FanoutExchange; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.core.QueueBuilder; import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler; import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy; @@ -51,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.PropertySource; import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.support.RetryTemplate; @@ -61,20 +51,19 @@ import org.springframework.util.ErrorHandler; */ @Slf4j @ComponentScan -@EnableConfigurationProperties({ AmqpProperties.class, AmqpDeadletterProperties.class }) +@Import(DmfAmqpDeclarationConfiguration.class) +@EnableConfigurationProperties(AmqpProperties.class) @ConditionalOnProperty(prefix = "hawkbit.dmf", name = "enabled", matchIfMissing = true) @PropertySource("classpath:/hawkbit-dmf-defaults.properties") public class DmfApiConfiguration { private final AmqpProperties amqpProperties; - private final AmqpDeadletterProperties amqpDeadletterProperties; private final ConnectionFactory rabbitConnectionFactory; public DmfApiConfiguration( - final AmqpProperties amqpProperties, final AmqpDeadletterProperties amqpDeadletterProperties, + final AmqpProperties amqpProperties, final ConnectionFactory rabbitConnectionFactory) { this.amqpProperties = amqpProperties; - this.amqpDeadletterProperties = amqpDeadletterProperties; this.rabbitConnectionFactory = rabbitConnectionFactory; } @@ -98,19 +87,6 @@ public class DmfApiConfiguration { return new ConditionalRejectingErrorHandler(new RequeueExceptionStrategy(fatalExceptionStrategies, fatalExceptionTypes)); } - /** - * Create a {@link RabbitAdmin} and ignore declaration exceptions. - * {@link RabbitAdmin#setIgnoreDeclarationExceptions(boolean)} - * - * @return the bean - */ - @Bean - public RabbitAdmin rabbitAdmin() { - final RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitConnectionFactory); - rabbitAdmin.setIgnoreDeclarationExceptions(true); - return rabbitAdmin; - } - /** * @return {@link RabbitTemplate} with automatic retry, published confirms and {@link Jackson2JsonMessageConverter}. */ @@ -134,84 +110,6 @@ public class DmfApiConfiguration { return rabbitTemplate; } - /** - * Create the DMF API receiver queue for retrieving DMF messages. - * - * @return the receiver queue - */ - @Bean - public Queue dmfReceiverQueue() { - return new Queue( - amqpProperties.getReceiverQueue(), - true, false, false, - amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange())); - } - - /** - * Create the DMF API receiver queue for authentication requests called by 3rd - * party artifact storages for download authorization by devices. - * - * @return the receiver queue - */ - @Bean - public Queue authenticationReceiverQueue() { - return QueueBuilder.nonDurable(amqpProperties.getAuthenticationReceiverQueue()) - .autoDelete() - .withArguments(getTTLMaxArgsAuthenticationQueue()) - .build(); - } - - /** - * Create DMF exchange. - * - * @return the fanout exchange - */ - @Bean - public FanoutExchange dmfSenderExchange() { - return new FanoutExchange(AmqpSettings.DMF_EXCHANGE); - } - - /** - * Create the Binding {@link DmfApiConfiguration#dmfReceiverQueue()} to - * {@link DmfApiConfiguration#dmfSenderExchange()}. - * - * @return the binding and create the queue and exchange - */ - @Bean - public Binding bindDmfSenderExchangeToDmfQueue() { - return BindingBuilder.bind(dmfReceiverQueue()).to(dmfSenderExchange()); - } - - /** - * Create dead letter queue. - * - * @return the queue - */ - @Bean - public Queue deadLetterQueue() { - return amqpDeadletterProperties.createDeadletterQueue(amqpProperties.getDeadLetterQueue()); - } - - /** - * Create the dead letter fanout exchange. - * - * @return the fanout exchange - */ - @Bean - public FanoutExchange deadLetterExchange() { - return new FanoutExchange(amqpProperties.getDeadLetterExchange()); - } - - /** - * Create the Binding deadLetterQueue to deadLetterExchange. - * - * @return the binding - */ - @Bean - public Binding bindDeadLetterQueueToDeadLetterExchange() { - return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()); - } - /** * Create AMQP handler service bean. * @@ -271,13 +169,6 @@ public class DmfApiConfiguration { deploymentManagement); } - private static Map getTTLMaxArgsAuthenticationQueue() { - final Map args = new HashMap<>(2); - args.put("x-message-ttl", Duration.ofSeconds(30).toMillis()); - args.put("x-max-length", 1_000); - return args; - } - @ToString private static class SqlFatalExceptionStrategy implements FatalExceptionStrategy { @@ -312,4 +203,5 @@ public class DmfApiConfiguration { return false; } } + } \ No newline at end of file