InventoryAsyncConfiguration.java

  1. /*
  2.  * Copyright 2005-2025 the original author or authors.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  * http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package org.openwms.wms.app;

  17. import org.ameba.amqp.RabbitTemplateConfigurable;
  18. import org.openwms.core.SpringProfiles;
  19. import org.slf4j.Logger;
  20. import org.slf4j.LoggerFactory;
  21. import org.springframework.amqp.core.Binding;
  22. import org.springframework.amqp.core.BindingBuilder;
  23. import org.springframework.amqp.core.DirectExchange;
  24. import org.springframework.amqp.core.Queue;
  25. import org.springframework.amqp.core.QueueBuilder;
  26. import org.springframework.amqp.core.TopicExchange;
  27. import org.springframework.amqp.rabbit.annotation.EnableRabbit;
  28. import org.springframework.amqp.rabbit.connection.ConnectionFactory;
  29. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  30. import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
  31. import org.springframework.amqp.support.converter.MessageConverter;
  32. import org.springframework.amqp.support.converter.SerializerMessageConverter;
  33. import org.springframework.beans.factory.ObjectProvider;
  34. import org.springframework.beans.factory.annotation.Autowired;
  35. import org.springframework.beans.factory.annotation.Qualifier;
  36. import org.springframework.beans.factory.annotation.Value;
  37. import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  38. import org.springframework.cloud.context.config.annotation.RefreshScope;
  39. import org.springframework.context.annotation.Bean;
  40. import org.springframework.context.annotation.Configuration;
  41. import org.springframework.context.annotation.Primary;
  42. import org.springframework.context.annotation.Profile;
  43. import org.springframework.retry.backoff.ExponentialBackOffPolicy;
  44. import org.springframework.retry.support.RetryTemplate;

  45. import java.util.Objects;

  46. import static org.ameba.LoggingCategories.BOOT;

  47. /**
  48.  * A InventoryAsyncConfiguration is activated when the service uses asynchronous communication to access other services.
  49.  *
  50.  * @author Heiko Scherrer
  51.  */
  52. @Profile(SpringProfiles.ASYNCHRONOUS_PROFILE)
  53. @Configuration
  54. @RefreshScope
  55. @EnableRabbit
  56. class InventoryAsyncConfiguration {

  57.     private static final Logger BOOT_LOGGER = LoggerFactory.getLogger(BOOT);
  58.     private static final String X_DEAD_LETTER_EXCHANGE = "x-dead-letter-exchange";
  59.     private static final String X_DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";
  60.     private static final String POISON_MESSAGE = "poison-message";

  61.     @ConditionalOnExpression("'${owms.inventory.serialization}'=='json'")
  62.     @Bean
  63.     MessageConverter messageConverter() {
  64.         var messageConverter = new Jackson2JsonMessageConverter();
  65.         BOOT_LOGGER.info("Using JSON serialization over AMQP");
  66.         return messageConverter;
  67.     }

  68.     @ConditionalOnExpression("'${owms.inventory.serialization}'=='barray'")
  69.     @Bean
  70.     MessageConverter serializerMessageConverter() {
  71.         var messageConverter = new SerializerMessageConverter();
  72.         BOOT_LOGGER.info("Using byte array serialization over AMQP");
  73.         return messageConverter;
  74.     }

  75.     @Primary
  76.     @Bean(name = "amqpTemplate")
  77.     public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
  78.             ObjectProvider<MessageConverter> messageConverter,
  79.             @Autowired(required = false) RabbitTemplateConfigurable rabbitTemplateConfigurable) {
  80.         var rabbitTemplate = new RabbitTemplate(connectionFactory);
  81.         rabbitTemplate.setObservationEnabled(true);
  82.         var backOffPolicy = new ExponentialBackOffPolicy();
  83.         backOffPolicy.setMultiplier(2);
  84.         backOffPolicy.setMaxInterval(15000);
  85.         backOffPolicy.setInitialInterval(500);
  86.         var retryTemplate = new RetryTemplate();
  87.         retryTemplate.setBackOffPolicy(backOffPolicy);
  88.         rabbitTemplate.setRetryTemplate(retryTemplate);
  89.         rabbitTemplate.setMessageConverter(Objects.requireNonNull(messageConverter.getIfUnique()));
  90.         if (rabbitTemplateConfigurable != null) {
  91.             rabbitTemplateConfigurable.configure(rabbitTemplate);
  92.         }
  93.         return rabbitTemplate;
  94.     }

  95.     /*~ --------------- Exchanges --------------- */
  96.     @RefreshScope
  97.     @Bean
  98.     TopicExchange inventoryExchange(@Value("${owms.events.inventory.exchange-name}") String exchangeName) {
  99.         return new TopicExchange(exchangeName, true, false);
  100.     }
  101.     @RefreshScope
  102.     @Bean
  103.     TopicExchange tuExchange(@Value("${owms.events.common.tu.exchange-name}") String exchangeName) {
  104.         return new TopicExchange(exchangeName, true, false);
  105.     }
  106.     @RefreshScope
  107.     @Bean
  108.     TopicExchange commonLgExchange(@Value("${owms.events.common.lg.exchange-name}") String exchangeName) {
  109.         return new TopicExchange(exchangeName, true, false);
  110.     }
  111.     @RefreshScope
  112.     @Bean
  113.     TopicExchange commonTuCommandsExchange(@Value("${owms.commands.common.tu.exchange-name}") String exchangeName) {
  114.         return new TopicExchange(exchangeName, true, false);
  115.     }
  116.     @RefreshScope
  117.     @Bean
  118.     DirectExchange inventoryCommandsExchange(@Value("${owms.commands.inventory.exchange-name}") String exchangeName) {
  119.         return new DirectExchange(exchangeName, true, false);
  120.     }
  121.     @RefreshScope
  122.     @Bean
  123.     TopicExchange shippingExchange(@Value("${owms.events.shipping.exchange-name}") String exchangeName) {
  124.         return new TopicExchange(exchangeName, true, false);
  125.     }
  126.     @RefreshScope
  127.     @Bean
  128.     TopicExchange movementExchange(@Value("${owms.events.movement.exchange-name}") String exchangeName) {
  129.         return new TopicExchange(exchangeName, true, false);
  130.     }

  131.     /*~ ---------------- Queues ----------------- */
  132.     @Bean
  133.     Queue tuQueue(
  134.             @Value("${owms.events.inventory.tu.queue-name}") String queueName,
  135.             @Value("${owms.dead-letter.exchange-name}") String exchangeName
  136.     ) {
  137.         return QueueBuilder.durable(queueName)
  138.                 .withArgument(X_DEAD_LETTER_EXCHANGE, exchangeName)
  139.                 .withArgument(X_DEAD_LETTER_ROUTING_KEY, POISON_MESSAGE)
  140.                 .build();
  141.     }
  142.     @Bean
  143.     Queue eventsLgQueue(
  144.             @Value("${owms.events.inventory.lg.queue-name}") String queueName,
  145.             @Value("${owms.dead-letter.exchange-name}") String exchangeName
  146.     ) {
  147.         return QueueBuilder.durable(queueName)
  148.                 .withArgument(X_DEAD_LETTER_EXCHANGE, exchangeName)
  149.                 .withArgument(X_DEAD_LETTER_ROUTING_KEY, POISON_MESSAGE)
  150.                 .build();
  151.     }
  152.     @Bean
  153.     Queue tuCommandsQueue(
  154.             @Value("${owms.commands.inventory.tu.queue-name}") String queueName,
  155.             @Value("${owms.dead-letter.exchange-name}") String exchangeName
  156.     ) {
  157.         return QueueBuilder.durable(queueName)
  158.                 .withArgument(X_DEAD_LETTER_EXCHANGE, exchangeName)
  159.                 .withArgument(X_DEAD_LETTER_ROUTING_KEY, POISON_MESSAGE)
  160.                 .build();
  161.     }
  162.     @Bean
  163.     Queue puCommandsQueue(
  164.             @Value("${owms.commands.inventory.pu.queue-name}") String queueName,
  165.             @Value("${owms.dead-letter.exchange-name}") String exchangeName
  166.     ) {
  167.         return QueueBuilder.durable(queueName)
  168.                 .withArgument(X_DEAD_LETTER_EXCHANGE, exchangeName)
  169.                 .withArgument(X_DEAD_LETTER_ROUTING_KEY, POISON_MESSAGE)
  170.                 .build();
  171.     }
  172.     @Bean
  173.     Queue shippingSplitQueue(
  174.             @Value("${owms.events.shipping.split.queue-name}") String queueName,
  175.             @Value("${owms.dead-letter.exchange-name}") String exchangeName
  176.     ) {
  177.         return QueueBuilder.durable(queueName)
  178.                 .withArgument(X_DEAD_LETTER_EXCHANGE, exchangeName)
  179.                 .withArgument(X_DEAD_LETTER_ROUTING_KEY, POISON_MESSAGE)
  180.                 .build();
  181.     }
  182.     @Bean
  183.     Queue movementQueue(
  184.             @Value("${owms.events.movement.queue-name}") String queueName,
  185.             @Value("${owms.dead-letter.exchange-name}") String exchangeName
  186.     ) {
  187.         return QueueBuilder.durable(queueName)
  188.                 .withArgument(X_DEAD_LETTER_EXCHANGE, exchangeName)
  189.                 .withArgument(X_DEAD_LETTER_ROUTING_KEY, POISON_MESSAGE)
  190.                 .build();
  191.     }

  192.     /*~ --------------- Bindings ---------------- */
  193.     @RefreshScope
  194.     @Bean
  195.     Binding tuBinding(
  196.             @Qualifier("tuExchange") TopicExchange tuExchange,
  197.             @Qualifier("tuQueue") Queue tuQueue,
  198.             @Value("${owms.events.inventory.tu.routing-key}") String routingKey
  199.     ) {
  200.         return BindingBuilder
  201.                 .bind(tuQueue)
  202.                 .to(tuExchange)
  203.                 .with(routingKey);
  204.     }
  205.     @RefreshScope
  206.     @Bean
  207.     Binding lgBinding(
  208.             @Qualifier("commonLgExchange") TopicExchange commonLgExchange,
  209.             @Qualifier("eventsLgQueue") Queue eventsLgQueue,
  210.             @Value("${owms.events.inventory.lg.routing-key}") String routingKey
  211.     ) {
  212.         return BindingBuilder
  213.                 .bind(eventsLgQueue)
  214.                 .to(commonLgExchange)
  215.                 .with(routingKey);
  216.     }
  217.     @RefreshScope
  218.     @Bean
  219.     Binding tuCommandsBinding(
  220.             @Qualifier("commonTuCommandsExchange") TopicExchange commonTuCommandsExchange,
  221.             @Qualifier("tuCommandsQueue") Queue tuCommandsQueue,
  222.             @Value("${owms.commands.inventory.tu.routing-key}") String routingKey
  223.     ) {
  224.         return BindingBuilder
  225.                 .bind(tuCommandsQueue)
  226.                 .to(commonTuCommandsExchange)
  227.                 .with(routingKey);
  228.     }
  229.     @RefreshScope
  230.     @Bean
  231.     Binding puCommandsBinding(
  232.             @Qualifier("inventoryCommandsExchange") DirectExchange inventoryCommandsExchange,
  233.             @Qualifier("puCommandsQueue") Queue puCommandsQueue,
  234.             @Value("${owms.commands.inventory.pu.routing-key}") String routingKey
  235.     ) {
  236.         return BindingBuilder
  237.                 .bind(puCommandsQueue)
  238.                 .to(inventoryCommandsExchange)
  239.                 .with(routingKey);
  240.     }
  241.     @RefreshScope
  242.     @Bean
  243.     Binding splitBinding(
  244.             @Qualifier("shippingExchange") TopicExchange shippingExchange,
  245.             @Qualifier("shippingSplitQueue") Queue shippingSplitQueue,
  246.             @Value("${owms.events.shipping.split.routing-key}") String routingKey
  247.     ) {
  248.         return BindingBuilder
  249.                 .bind(shippingSplitQueue)
  250.                 .to(shippingExchange)
  251.                 .with(routingKey);
  252.     }
  253.     @RefreshScope
  254.     @Bean
  255.     Binding movementBinding(
  256.             @Qualifier("movementExchange") TopicExchange movementExchange,
  257.             @Qualifier("movementQueue") Queue movementQueue,
  258.             @Value("${owms.events.movement.routing-key}") String routingKey
  259.     ) {
  260.         return BindingBuilder
  261.                 .bind(movementQueue)
  262.                 .to(movementExchange)
  263.                 .with(routingKey);
  264.     }

  265.     /* Dead Letter */
  266.     @RefreshScope
  267.     @Bean
  268.     DirectExchange dlExchange(@Value("${owms.dead-letter.exchange-name}") String exchangeName) {
  269.         return new DirectExchange(exchangeName);
  270.     }

  271.     @RefreshScope
  272.     @Bean
  273.     Queue dlQueue(@Value("${owms.dead-letter.queue-name}") String queueName) {
  274.         return QueueBuilder.durable(queueName).build();
  275.     }

  276.     @RefreshScope
  277.     @Bean
  278.     Binding dlBinding(
  279.             @Value("${owms.dead-letter.queue-name}") String queueName,
  280.             @Value("${owms.dead-letter.exchange-name}") String exchangeName) {
  281.         return BindingBuilder
  282.                 .bind(dlQueue(queueName))
  283.                 .to(dlExchange(exchangeName))
  284.                 .with(POISON_MESSAGE);
  285.     }
  286. }