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 ad1613ef4..48cfb33cf 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
@@ -27,6 +27,10 @@ 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.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
@@ -34,6 +38,8 @@ import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.amqp.autoconfigure.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -89,13 +95,19 @@ public class DmfApiConfiguration {
return new ConditionalRejectingErrorHandler(new RequeueExceptionStrategy(fatalExceptionStrategies, fatalExceptionTypes));
}
+ @Bean
+ @ConditionalOnMissingBean(name = "amqpMessageConverter") // override it if needed to add / edit trusted packages or need other customization
+ public MessageConverter amqpMessageConverter(final JsonMapper jsonMapper) {
+ return DmfApiConfiguration.messageConverter(jsonMapper);
+ }
+
/**
* @return {@link RabbitTemplate} with automatic retry, published confirms and {@link JacksonJsonMessageConverter}.
*/
@Bean
- public RabbitTemplate rabbitTemplate(final JsonMapper jsonMapper) {
+ public RabbitTemplate rabbitTemplate(@Qualifier("amqpMessageConverter") final MessageConverter amqpMessageConverter) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory);
- rabbitTemplate.setMessageConverter(new JacksonJsonMessageConverter(jsonMapper));
+ rabbitTemplate.setMessageConverter(amqpMessageConverter);
// the same policy the previously used default ExponentialBackOffPolicy applied
rabbitTemplate.setRetryTemplate(new RetryTemplate(RetryPolicy.builder()
@@ -168,12 +180,38 @@ public class DmfApiConfiguration {
final SystemManagement systemManagement,
final TargetManagement extends Target> targetManagement,
final DistributionSetManagement extends DistributionSet> distributionSetManagement,
- final SoftwareModuleManagement extends SoftwareModule> softwareModuleManagement, final DeploymentManagement deploymentManagement) {
+ final SoftwareModuleManagement extends SoftwareModule> softwareModuleManagement,
+ final DeploymentManagement deploymentManagement) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemManagement, targetManagement, softwareModuleManagement, distributionSetManagement,
deploymentManagement);
}
+ // since spring-amqp 4.0.4 not all packages are assumed trusted for type converter (only java.land and java.util)
+ // so e need to add hawkbit (and eventual extension packages as trusted
+ // also (again since spring-amqp 4.0.4) the conversion from empty payload fail (which probably is fine since it is JSON)
+ // however, (for backward compatibility, e.g. THING_REMOVED doesn't define payload and could be empty byte[]) we assume that
+ // empty payload is empty byte[] and not try to convert it to Object (which fail since it is not JSON)
+ static @NonNull JacksonJsonMessageConverter messageConverter(final JsonMapper jsonMapper) {
+ return messageConverter(jsonMapper, "org.eclipse.hawkbit.dmf.json.model");
+ }
+
+ public static @NonNull JacksonJsonMessageConverter messageConverter(final JsonMapper jsonMapper, final String... trustedPackages) {
+ return new JacksonJsonMessageConverter(jsonMapper, trustedPackages) {
+
+ @Override
+ public @NonNull Object fromMessage(@NonNull final Message message, final @Nullable Object conversionHint) {
+ // default converter tries to convert empty body payload to Object (since rabbit 4.0.4)
+ // which probably is correct since it has to be JSON - however, in this case we assume - empty byte[]
+ if (message.getBody().length == 0) {
+ return message.getBody();
+ } else {
+ return super.fromMessage(message, conversionHint);
+ }
+ }
+ };
+ }
+
@ToString
private static class SqlFatalExceptionStrategy implements FatalExceptionStrategy {
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java
index 1c53eb502..d1afe741e 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java
@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
+import org.eclipse.hawkbit.rabbitmq.test.AmqpTestConfiguration;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetManagement.Create;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
@@ -60,10 +61,10 @@ import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.DefaultJacksonJavaTypeMapper;
-import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
+import tools.jackson.databind.json.JsonMapper;
/**
* Feature: Component Tests - Device Management Federation API
@@ -94,7 +95,7 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
Create.builder().controllerId(CONTROLLER_ID).securityToken(TEST_TOKEN).address(AMQP_URI.toString()).build());
this.rabbitTemplate = Mockito.mock(RabbitTemplate.class);
- when(rabbitTemplate.getMessageConverter()).thenReturn(new JacksonJsonMessageConverter());
+ when(rabbitTemplate.getMessageConverter()).thenReturn(AmqpTestConfiguration.messageConverter(new JsonMapper()));
senderService = Mockito.mock(DefaultAmqpMessageSenderService.class);
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
index ca0578c4b..bfa73103e 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
@@ -54,9 +54,9 @@ import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
-import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
+import tools.jackson.databind.json.JsonMapper;
/**
* Feature: Component Tests - Device Management Federation API
@@ -108,7 +108,7 @@ class AmqpMessageHandlerServiceTest {
@SuppressWarnings({ "rawtypes", "unchecked" })
void before() {
TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement);
- messageConverter = new JacksonJsonMessageConverter();
+ messageConverter = DmfApiConfiguration.messageConverter(new JsonMapper());
lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java
index 5f8a794b4..bb1cb4067 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java
@@ -28,8 +28,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
-import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
+import tools.jackson.databind.json.JsonMapper;
/**
* Feature: Component Tests - Device Management Federation API
@@ -54,7 +54,7 @@ class BaseAmqpServiceTest {
@Test
void convertMessageTest() {
final DmfActionUpdateStatus actionUpdateStatus = createActionStatus();
- when(rabbitTemplate.getMessageConverter()).thenReturn(new JacksonJsonMessageConverter());
+ when(rabbitTemplate.getMessageConverter()).thenReturn(DmfApiConfiguration.messageConverter(new JsonMapper()));
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus, createJsonProperties());
final DmfActionUpdateStatus convertedActionUpdateStatus = baseAmqpService.convertMessage(message, DmfActionUpdateStatus.class);
@@ -91,7 +91,7 @@ class BaseAmqpServiceTest {
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
void updateActionStatusWithInvalidJsonContent() {
final Message message = createMessage("Invalid Json".getBytes());
- when(rabbitTemplate.getMessageConverter()).thenReturn(new JacksonJsonMessageConverter());
+ when(rabbitTemplate.getMessageConverter()).thenReturn(DmfApiConfiguration.messageConverter(new JsonMapper()));
assertThatExceptionOfType(MessageConversionException.class)
.as("Expected MessageConversionException for invalid JSON")
diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AbstractAmqpIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AbstractAmqpIntegrationTest.java
index a4e2e8e17..26063579d 100644
--- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AbstractAmqpIntegrationTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AbstractAmqpIntegrationTest.java
@@ -25,12 +25,12 @@ 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.junit.RabbitAvailable;
-import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
+import tools.jackson.databind.json.JsonMapper;
@Slf4j
@RabbitAvailable
@@ -88,7 +88,7 @@ public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTes
private RabbitTemplate createDmfClient() {
final RabbitTemplate template = new RabbitTemplate(connectionFactory);
- template.setMessageConverter(new JacksonJsonMessageConverter());
+ template.setMessageConverter(AmqpTestConfiguration.messageConverter(new JsonMapper()));
template.setReceiveTimeout(TimeUnit.SECONDS.toMillis(3));
template.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
template.setExchange(getExchange());
diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java
index 90015ad2a..b346839ec 100644
--- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java
+++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/src/main/java/org/eclipse/hawkbit/rabbitmq/test/AmqpTestConfiguration.java
@@ -14,6 +14,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
@@ -24,6 +27,7 @@ import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
+import tools.jackson.databind.json.JsonMapper;
@Configuration
public class AmqpTestConfiguration {
@@ -32,7 +36,7 @@ public class AmqpTestConfiguration {
@Primary
public RabbitTemplate rabbitTemplateForTest(final ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
- rabbitTemplate.setMessageConverter(new JacksonJsonMessageConverter());
+ rabbitTemplate.setMessageConverter(messageConverter(new JsonMapper()));
rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
rabbitTemplate.setReceiveTimeout(TimeUnit.SECONDS.toMillis(3));
return rabbitTemplate;
@@ -67,4 +71,19 @@ public class AmqpTestConfiguration {
RabbitMqSetupService rabbitMqSetupService() {
return new RabbitMqSetupService();
}
+
+ // note - it MUST be the same as DmfApiConfiguration#messageConverter for the test to work properly (to test the real AMQP)
+ public static @NonNull JacksonJsonMessageConverter messageConverter(final JsonMapper jsonMapper) {
+ return new JacksonJsonMessageConverter(jsonMapper, "org.eclipse.hawkbit.dmf.json.model") {
+
+ @Override
+ public @NonNull Object fromMessage(@NonNull final Message message, final @Nullable Object conversionHint) {
+ if (message.getBody().length == 0) {
+ return message.getBody();
+ } else {
+ return super.fromMessage(message, conversionHint);
+ }
+ }
+ };
+ }
}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c34425484..e420051fb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
org.springframework.boot
spring-boot-starter-parent
- 4.0.6
+ 4.0.7
org.eclipse.hawkbit
@@ -58,7 +58,7 @@
17
- 4.0.6
+ 4.0.7
2025.1.1
1.1.7
3.0.3