Code refactoring of hawkbit-dmf-amqp (#2054)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-16 20:17:13 +02:00
committed by GitHub
parent ce846ebe81
commit ca2c50ffa5
21 changed files with 235 additions and 236 deletions

View File

@@ -9,7 +9,6 @@
SPDX-License-Identifier: EPL-2.0 SPDX-License-Identifier: EPL-2.0
--> -->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"> xmlns="http://maven.apache.org/POM/4.0.0">
@@ -39,17 +38,18 @@
<artifactId>hawkbit-repository-api</artifactId> <artifactId>hawkbit-repository-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.plugin</groupId> <groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId> <artifactId>spring-plugin-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Test --> <!-- Test -->
<dependency> <dependency>
<groupId>org.eclipse.hawkbit</groupId> <groupId>org.eclipse.hawkbit</groupId>

View File

@@ -48,7 +48,5 @@ public class DDIStart {
@Configuration @Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true) @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public static class MethodSecurityConfig { public static class MethodSecurityConfig {}
}
} }

View File

@@ -16,7 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
@@ -30,8 +29,9 @@ public abstract class AbstractSecurityTest {
@BeforeEach @BeforeEach
public void setup() { public void setup() {
final DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context) mvc = MockMvcBuilders.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity()).dispatchOptions(true); .apply(SecurityMockMvcConfigurers.springSecurity())
mvc = builder.build(); .dispatchOptions(true)
.build();
} }
} }

View File

@@ -19,7 +19,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost", @TestPropertySource(properties = {
"hawkbit.server.security.allowedHostNames=localhost",
"hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" }) "hawkbit.server.security.httpFirewallIgnoredPaths=/index.html" })
@Feature("Integration Test - Security") @Feature("Integration Test - Security")
@Story("Allowed Host Names") @Story("Allowed Host Names")
@@ -28,13 +29,15 @@ public class AllowedHostNamesTest extends AbstractSecurityTest {
@Test @Test
@Description("Tests whether a RequestRejectedException is thrown when not allowed host is used") @Description("Tests whether a RequestRejectedException is thrown when not allowed host is used")
public void allowedHostNameWithNotAllowedHost() throws Exception { public void allowedHostNameWithNotAllowedHost() throws Exception {
mvc.perform(get("/").header(HttpHeaders.HOST, "www.google.com")).andExpect(status().isBadRequest()); mvc.perform(get("/").header(HttpHeaders.HOST, "www.google.com"))
.andExpect(status().isBadRequest());
} }
@Test @Test
@Description("Tests whether request is redirected when allowed host is used") @Description("Tests whether request is redirected when allowed host is used")
public void allowedHostNameWithAllowedHost() throws Exception { public void allowedHostNameWithAllowedHost() throws Exception {
mvc.perform(get("/").header(HttpHeaders.HOST, "localhost")).andExpect(status().is3xxRedirection()); mvc.perform(get("/").header(HttpHeaders.HOST, "localhost"))
.andExpect(status().is3xxRedirection());
} }
@Test @Test

View File

@@ -30,7 +30,8 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Description("Tests whether request fail if a role is forbidden for the user") @Description("Tests whether request fail if a role is forbidden for the user")
@WithUser(authorities = { SpPermission.READ_TARGET }) @WithUser(authorities = { SpPermission.READ_TARGET })
public void failIfNoRole() throws Exception { public void failIfNoRole() throws Exception {
mvc.perform(get("/DEFAULT/controller/v1/controllerId")).andExpect(result -> mvc.perform(get("/DEFAULT/controller/v1/controllerId"))
.andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value())); assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
} }
@@ -38,7 +39,8 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Description("Tests whether request succeed if a role is granted for the user") @Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpPermission.SpringEvalExpressions.CONTROLLER_ROLE }) @WithUser(authorities = { SpPermission.SpringEvalExpressions.CONTROLLER_ROLE })
public void successIfHasRole() throws Exception { public void successIfHasRole() throws Exception {
mvc.perform(get("/DEFAULT/controller/v1/controllerId")).andExpect(result -> { mvc.perform(get("/DEFAULT/controller/v1/controllerId"))
.andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
}); });
} }

View File

@@ -9,7 +9,6 @@
SPDX-License-Identifier: EPL-2.0 SPDX-License-Identifier: EPL-2.0
--> -->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -43,6 +42,7 @@
<artifactId>hawkbit-dmf-api</artifactId> <artifactId>hawkbit-dmf-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId> <artifactId>spring-boot-autoconfigure</artifactId>
@@ -67,6 +67,7 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId> <artifactId>spring-boot-starter-logging</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId> <artifactId>commons-collections4</artifactId>

View File

@@ -18,8 +18,7 @@ public abstract class AbstractAmqpErrorHandler<T> implements AmqpErrorHandler {
@Override @Override
public void doHandle(Throwable throwable, AmqpErrorHandlerChain chain) { public void doHandle(Throwable throwable, AmqpErrorHandlerChain chain) {
// retrieving the cause of throwable as it contains the actual class of // retrieving the cause of throwable as it contains the actual class of exception
// exception
final Throwable cause = throwable.getCause(); final Throwable cause = throwable.getCause();
if (getExceptionClass().isAssignableFrom(cause.getClass())) { if (getExceptionClass().isAssignableFrom(cause.getClass())) {
throw new AmqpRejectAndDontRequeueException(getErrorMessage(throwable)); throw new AmqpRejectAndDontRequeueException(getErrorMessage(throwable));
@@ -43,5 +42,4 @@ public abstract class AbstractAmqpErrorHandler<T> implements AmqpErrorHandler {
public String getErrorMessage(Throwable throwable) { public String getErrorMessage(Throwable throwable) {
return AmqpErrorMessageComposer.constructErrorMessage(throwable); return AmqpErrorMessageComposer.constructErrorMessage(throwable);
} }
} }

View File

@@ -127,8 +127,7 @@ public class AmqpConfiguration {
} }
/** /**
* @return {@link RabbitTemplate} with automatic retry, published confirms and * @return {@link RabbitTemplate} with automatic retry, published confirms and {@link Jackson2JsonMessageConverter}.
* {@link Jackson2JsonMessageConverter}.
*/ */
@Bean @Bean
public RabbitTemplate rabbitTemplate() { public RabbitTemplate rabbitTemplate() {
@@ -246,7 +245,8 @@ public class AmqpConfiguration {
final SystemSecurityContext systemSecurityContext, final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement,
final ConfirmationManagement confirmationManagement) { final ConfirmationManagement confirmationManagement) {
return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement, return new AmqpMessageHandlerService(
rabbitTemplate, amqpMessageDispatcherService, controllerManagement,
entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement); entityFactory, systemSecurityContext, tenantConfigurationManagement, confirmationManagement);
} }
@@ -279,7 +279,8 @@ public class AmqpConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class) @ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, AmqpMessageDispatcherService amqpMessageDispatcherService(
final RabbitTemplate rabbitTemplate,
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement, final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement,

View File

@@ -28,15 +28,14 @@ public class AmqpDeadletterProperties {
private static final int THREE_WEEKS = 21; private static final int THREE_WEEKS = 21;
/** /**
* Message time to live (ttl) for the deadletter queue. Default ttl is 3 * Message time to live (ttl) for the dead letter queue. Default ttl is 3 weeks.
* weeks.
*/ */
private long ttl = Duration.ofDays(THREE_WEEKS).toMillis(); private long ttl = Duration.ofDays(THREE_WEEKS).toMillis();
/** /**
* Return the deadletter arguments. * Return the dead letter arguments.
* *
* @param exchange the deadletter exchange * @param exchange the dead letter exchange
* @return map which holds the properties * @return map which holds the properties
*/ */
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) { public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
@@ -46,10 +45,10 @@ public class AmqpDeadletterProperties {
} }
/** /**
* Create a deadletter queue with ttl for messages * Create a dead letter queue with ttl for messages
* *
* @param queueName the deadletter queue name * @param queueName the dead letter queue name
* @return the deadletter queue * @return the dead letter queue
*/ */
public Queue createDeadletterQueue(final String queueName) { public Queue createDeadletterQueue(final String queueName) {
return new Queue(queueName, true, false, false, getTTLArgs()); return new Queue(queueName, true, false, false, getTTLArgs());

View File

@@ -10,8 +10,7 @@
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
/** /**
* Interface declaration of {@link AmqpErrorHandler} that handles errors based on the * Interface declaration of {@link AmqpErrorHandler} that handles errors based on the types of exception.
* types of exception.
*/ */
@FunctionalInterface @FunctionalInterface
public interface AmqpErrorHandler { public interface AmqpErrorHandler {

View File

@@ -31,9 +31,7 @@ public final class AmqpErrorMessageComposer {
* @return meaningful error message * @return meaningful error message
*/ */
public static String constructErrorMessage(final Throwable throwable) { public static String constructErrorMessage(final Throwable throwable) {
StringBuilder completeErrorMessage = new StringBuilder();
final String mainErrorMsg = throwable.getCause().getMessage(); final String mainErrorMsg = throwable.getCause().getMessage();
if (throwable instanceof ListenerExecutionFailedException) { if (throwable instanceof ListenerExecutionFailedException) {
Collection<Message> failedMessages = ((ListenerExecutionFailedException) throwable).getFailedMessages(); Collection<Message> failedMessages = ((ListenerExecutionFailedException) throwable).getFailedMessages();
// since the intended message content is always on top of the collection, we only extract the first one // since the intended message content is always on top of the collection, we only extract the first one
@@ -41,11 +39,10 @@ public final class AmqpErrorMessageComposer {
final byte[] amqpFailedMsgBody = failedMessage.getBody(); final byte[] amqpFailedMsgBody = failedMessage.getBody();
final Map<String, Object> amqpFailedMsgHeaders = failedMessage.getMessageProperties().getHeaders(); final Map<String, Object> amqpFailedMsgHeaders = failedMessage.getMessageProperties().getHeaders();
String amqpFailedMsgConcatenatedHeaders = amqpFailedMsgHeaders.keySet().stream() final String amqpFailedMsgConcatenatedHeaders = amqpFailedMsgHeaders.keySet().stream()
.map(key -> key + "=" + amqpFailedMsgHeaders.get(key)).collect(Collectors.joining(", ", "{", "}")); .map(key -> key + "=" + amqpFailedMsgHeaders.get(key))
completeErrorMessage.append(mainErrorMsg).append(new String(amqpFailedMsgBody)) .collect(Collectors.joining(", ", "{", "}"));
.append(amqpFailedMsgConcatenatedHeaders); return mainErrorMsg + new String(amqpFailedMsgBody) + amqpFailedMsgConcatenatedHeaders;
return completeErrorMessage.toString();
} }
return mainErrorMsg; return mainErrorMsg;
} }

View File

@@ -84,8 +84,7 @@ import org.springframework.util.CollectionUtils;
* {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and * {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and
* delegate the messages to a {@link AmqpMessageSenderService}. * delegate the messages to a {@link AmqpMessageSenderService}.
* *
* Additionally the dispatcher listener/subscribe for some target events e.g. * Additionally, the dispatcher listener/subscribe for some target events e.g. assignment.
* assignment.
*/ */
@Slf4j @Slf4j
public class AmqpMessageDispatcherService extends BaseAmqpService { public class AmqpMessageDispatcherService extends BaseAmqpService {
@@ -112,12 +111,12 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* @param systemSecurityContext for execution with system permissions * @param systemSecurityContext for execution with system permissions
* @param systemManagement the systemManagement * @param systemManagement the systemManagement
* @param targetManagement to access target information * @param targetManagement to access target information
* @param serviceMatcher to check in cluster case if the message is from the same * @param serviceMatcher to check in cluster case if the message is from the same cluster node
* cluster node
* @param distributionSetManagement to retrieve modules * @param distributionSetManagement to retrieve modules
* @param tenantConfigurationManagement to access tenant configuration * @param tenantConfigurationManagement to access tenant configuration
*/ */
protected AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, protected AmqpMessageDispatcherService(
final RabbitTemplate rabbitTemplate,
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
final TargetManagement targetManagement, final ServiceMatcher serviceMatcher, final TargetManagement targetManagement, final ServiceMatcher serviceMatcher,
@@ -138,15 +137,15 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
public boolean isBatchAssignmentsEnabled() { public boolean isBatchAssignmentsEnabled() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement return systemSecurityContext.runAsSystem(() ->
tenantConfigurationManagement
.getConfigurationValue(BATCH_ASSIGNMENTS_ENABLED, Boolean.class).getValue()); .getConfigurationValue(BATCH_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
} }
/** /**
* Method to send a message to a RabbitMQ Exchange after the Distribution * Method to send a message to a RabbitMQ Exchange after the Distribution set has been assign to a Target.
* set has been assign to a Target.
* *
* @param assignedEvent the object to be send. * @param assignedEvent the object to be sent.
*/ */
@EventListener(classes = TargetAssignDistributionSetEvent.class) @EventListener(classes = TargetAssignDistributionSetEvent.class)
protected void targetAssignDistributionSet(final TargetAssignDistributionSetEvent assignedEvent) { protected void targetAssignDistributionSet(final TargetAssignDistributionSetEvent assignedEvent) {
@@ -154,13 +153,11 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return; return;
} }
final List<Target> filteredTargetList = getTargetsWithoutPendingCancellations( final List<Target> filteredTargetList = getTargetsWithoutPendingCancellations(assignedEvent.getActions().keySet());
assignedEvent.getActions().keySet());
if (!filteredTargetList.isEmpty()) { if (!filteredTargetList.isEmpty()) {
log.debug("targetAssignDistributionSet retrieved. I will forward it to DMF broker."); log.debug("targetAssignDistributionSet retrieved. I will forward it to DMF broker.");
sendUpdateMessageToTargets(assignedEvent.getDistributionSetId(), assignedEvent.getActions(), sendUpdateMessageToTargets(assignedEvent.getDistributionSetId(), assignedEvent.getActions(), filteredTargetList);
filteredTargetList);
} }
} }
@@ -178,16 +175,17 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
sendMultiActionRequestMessages(multiActionEvent.getTenant(), multiActionEvent.getControllerIds()); sendMultiActionRequestMessages(multiActionEvent.getTenant(), multiActionEvent.getControllerIds());
} }
protected void sendUpdateMessageToTarget(final ActionProperties actionsProps, final Target target, protected void sendUpdateMessageToTarget(
final ActionProperties actionsProps, final Target target,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
final Map<String, ActionProperties> actionProp = new HashMap<>(); final Map<String, ActionProperties> actionProp = new HashMap<>();
actionProp.put(target.getControllerId(), actionsProps); actionProp.put(target.getControllerId(), actionsProps);
sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules); sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules);
} }
protected void sendMultiActionRequestToTarget(final String tenant, final Target target, final List<Action> actions, protected void sendMultiActionRequestToTarget(
final String tenant, final Target target, final List<Action> actions,
final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> getSoftwareModuleMetaData) { final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> getSoftwareModuleMetaData) {
final URI targetAddress = target.getAddress(); final URI targetAddress = target.getAddress();
if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) { if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) {
return; return;
@@ -195,26 +193,26 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest(); final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
actions.forEach(action -> { actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(target, action, final DmfActionRequest actionRequest = createDmfActionRequest(target, action, getSoftwareModuleMetaData.apply(action));
getSoftwareModuleMetaData.apply(action));
final int weight = deploymentManagement.getWeightConsideringDefault(action); final int weight = deploymentManagement.getWeightConsideringDefault(action);
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight); multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight);
}); });
final Message message = getMessageConverter().toMessage(multiActionRequest, final Message message = getMessageConverter().toMessage(
multiActionRequest,
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION)); createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION));
amqpSenderService.sendMessage(message, targetAddress); amqpSenderService.sendMessage(message, targetAddress);
} }
protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest(final Target target, final Long actionId, protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest(
final Target target, final Long actionId,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
final DmfDownloadAndUpdateRequest request = new DmfDownloadAndUpdateRequest(); final DmfDownloadAndUpdateRequest request = new DmfDownloadAndUpdateRequest();
request.setActionId(actionId); request.setActionId(actionId);
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken)); request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
if (softwareModules != null) { if (softwareModules != null) {
softwareModules.entrySet() softwareModules.entrySet().forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
} }
return request; return request;
} }
@@ -231,24 +229,22 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return; return;
} }
final List<Target> eventTargets = partitionedParallelExecution(cancelEvent.getActions().keySet(), final List<Target> eventTargets = partitionedParallelExecution(
targetManagement::getByControllerID); cancelEvent.getActions().keySet(), targetManagement::getByControllerID);
eventTargets.forEach(target -> { eventTargets.forEach(target ->
cancelEvent.getActionPropertiesForController(target.getControllerId()).map(ActionProperties::getId) cancelEvent.getActionPropertiesForController(target.getControllerId())
.ifPresent(actionId -> { .map(ActionProperties::getId)
sendCancelMessageToTarget(cancelEvent.getTenant(), target.getControllerId(), actionId, .ifPresent(actionId ->
target.getAddress()); sendCancelMessageToTarget(cancelEvent.getTenant(), target.getControllerId(), actionId, target.getAddress())
}); )
}); );
} }
/** /**
* Method to send a message to a RabbitMQ Exchange after a Target was * Method to send a message to a RabbitMQ Exchange after a Target was deleted.
* deleted.
* *
* @param deleteEvent the TargetDeletedEvent which holds the necessary data for * @param deleteEvent the TargetDeletedEvent which holds the necessary data for sending a target delete message.
* sending a target delete message.
*/ */
@EventListener(classes = TargetDeletedEvent.class) @EventListener(classes = TargetDeletedEvent.class)
protected void targetDelete(final TargetDeletedEvent deleteEvent) { protected void targetDelete(final TargetDeletedEvent deleteEvent) {
@@ -260,15 +256,18 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
@EventListener(classes = TargetAttributesRequestedEvent.class) @EventListener(classes = TargetAttributesRequestedEvent.class)
protected void targetTriggerUpdateAttributes(final TargetAttributesRequestedEvent updateAttributesEvent) { protected void targetTriggerUpdateAttributes(final TargetAttributesRequestedEvent updateAttributesEvent) {
sendUpdateAttributesMessageToTarget(updateAttributesEvent.getTenant(), updateAttributesEvent.getControllerId(), sendUpdateAttributesMessageToTarget(
updateAttributesEvent.getTenant(), updateAttributesEvent.getControllerId(),
updateAttributesEvent.getTargetAddress()); updateAttributesEvent.getTargetAddress());
} }
protected void sendPingReponseToDmfReceiver(final Message ping, final String tenant, final String virtualHost) { protected void sendPingResponseToDmfReceiver(final Message ping, final String tenant, final String virtualHost) {
final Message message = MessageBuilder.withBody(String.valueOf(System.currentTimeMillis()).getBytes()) final Message message = MessageBuilder
.withBody(String.valueOf(System.currentTimeMillis()).getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
.setCorrelationId(ping.getMessageProperties().getCorrelationId()) .setCorrelationId(ping.getMessageProperties().getCorrelationId())
.setHeader(MessageHeaderKey.TYPE, MessageType.PING_RESPONSE).setHeader(MessageHeaderKey.TENANT, tenant) .setHeader(MessageHeaderKey.TYPE, MessageType.PING_RESPONSE)
.setHeader(MessageHeaderKey.TENANT, tenant)
.build(); .build();
amqpSenderService.sendMessage(message, amqpSenderService.sendMessage(message,
@@ -279,8 +278,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return isFromSelf(event); return isFromSelf(event);
} }
protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId, protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId, final URI address) {
final URI address) {
if (!IpUtil.isAmqpUri(address)) { if (!IpUtil.isAmqpUri(address)) {
return; return;
} }
@@ -288,11 +286,11 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final DmfActionRequest actionRequest = new DmfActionRequest(); final DmfActionRequest actionRequest = new DmfActionRequest();
actionRequest.setActionId(actionId); actionRequest.setActionId(actionId);
final Message message = getMessageConverter().toMessage(actionRequest, final Message message = getMessageConverter().toMessage(
actionRequest,
createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD)); createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD));
amqpSenderService.sendMessage(message, address); amqpSenderService.sendMessage(message, address);
} }
protected DmfTarget convertToDmfTarget(final Target target, final Long actionId) { protected DmfTarget convertToDmfTarget(final Target target, final Long actionId) {
@@ -309,18 +307,18 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* @param target the target * @param target the target
* @param actionId the actionId * @param actionId the actionId
* @param softwareModules the software modules * @param softwareModules the software modules
* @return * @return confirm request
*/ */
protected DmfConfirmRequest createConfirmRequest(final Target target, final Long actionId, final Map<SoftwareModule, protected DmfConfirmRequest createConfirmRequest(
List<SoftwareModuleMetadata>> softwareModules) { final Target target, final Long actionId, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
final DmfConfirmRequest request = new DmfConfirmRequest(); final DmfConfirmRequest request = new DmfConfirmRequest();
request.setActionId(actionId); request.setActionId(actionId);
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken)); request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
//Software modules can be filtered in the future exposing only the needed. //Software modules can be filtered in the future exposing only the needed.
if (softwareModules != null) { if (softwareModules != null) {
softwareModules.entrySet() softwareModules.entrySet().forEach(entry ->
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry))); request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
} }
return request; return request;
} }
@@ -332,12 +330,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
/** /**
* Method to get the type of event depending on whether the action is a * Method to get the type of event depending on whether the action is a DOWNLOAD_ONLY action or if it has a valid maintenance window
* DOWNLOAD_ONLY action or if it has a valid maintenance window available or * available or not based on defined maintenance schedule. In case of no maintenance schedule or if there is a valid window available,
* not based on defined maintenance schedule. In case of no maintenance * the topic {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else {@link EventTopic#DOWNLOAD} is returned.
* schedule or if there is a valid window available, the topic
* {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else
* {@link EventTopic#DOWNLOAD} is returned.
* *
* @param action current action properties. * @param action current action properties.
* @return {@link EventTopic} to use for message. * @return {@link EventTopic} to use for message.
@@ -352,8 +347,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
/** /**
* Determines the {@link EventTopic} for the given {@link Action}, depending * Determines the {@link EventTopic} for the given {@link Action}, depending on its action type.
* on its action type.
* *
* @param action to obtain the corresponding {@link EventTopic} for * @param action to obtain the corresponding {@link EventTopic} for
* @return the {@link EventTopic} for this action * @return the {@link EventTopic} for this action
@@ -365,8 +359,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return getEventTypeForTarget(new ActionProperties(action)); return getEventTypeForTarget(new ActionProperties(action));
} }
private static <T, R> List<R> partitionedParallelExecution(final Collection<T> controllerIds, private static <T, R> List<R> partitionedParallelExecution(
final Function<Collection<T>, List<R>> loadingFunction) { final Collection<T> controllerIds, final Function<Collection<T>, List<R>> loadingFunction) {
// Ensure not exceeding the max value of MAX_PROCESSING_SIZE // Ensure not exceeding the max value of MAX_PROCESSING_SIZE
if (controllerIds.size() > MAX_PROCESSING_SIZE) { if (controllerIds.size() > MAX_PROCESSING_SIZE) {
// Split the provided collection // Split the provided collection
@@ -392,16 +386,16 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
} }
private static MessageProperties createConnectorMessagePropertiesEvent(final String tenant, private static MessageProperties createConnectorMessagePropertiesEvent(
final String controllerId, final EventTopic topic) { final String tenant, final String controllerId, final EventTopic topic) {
final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId); final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId);
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic); messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT); messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
return messageProperties; return messageProperties;
} }
private static MessageProperties createConnectorMessagePropertiesDeleteThing(final String tenant, private static MessageProperties createConnectorMessagePropertiesDeleteThing(
final String controllerId) { final String tenant, final String controllerId) {
final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId); final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId);
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.THING_DELETED); messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.THING_DELETED);
return messageProperties; return messageProperties;
@@ -434,30 +428,28 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
private List<Target> getTargetsWithoutPendingCancellations(final Set<String> controllerIds) { private List<Target> getTargetsWithoutPendingCancellations(final Set<String> controllerIds) {
return partitionedParallelExecution(controllerIds, partition -> { return partitionedParallelExecution(controllerIds, partition ->
return targetManagement.getByControllerID(partition).stream().filter(target -> { targetManagement.getByControllerID(partition).stream()
.filter(target -> {
if (hasPendingCancellations(target.getId())) { if (hasPendingCancellations(target.getId())) {
log.debug("Target {} has pending cancellations. Will not send update message to it.", log.debug("Target {} has pending cancellations. Will not send update message to it.", target.getControllerId());
target.getControllerId());
return false; return false;
} }
return true; return true;
}).collect(Collectors.toList()); }).collect(Collectors.toList()));
});
} }
private void sendUpdateMessageToTargets(final Long dsId, final Map<String, ActionProperties> actionsPropsByTargetId, private void sendUpdateMessageToTargets(
final List<Target> targets) { final Long dsId, final Map<String, ActionProperties> actionsPropsByTargetId, final List<Target> targets) {
distributionSetManagement.get(dsId).ifPresent(ds -> { distributionSetManagement.get(dsId).ifPresent(ds -> {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules = getSoftwareModulesWithMetadata( final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules = getSoftwareModulesWithMetadata(ds);
ds);
sendUpdateMessageToTargets(actionsPropsByTargetId, targets, softwareModules); sendUpdateMessageToTargets(actionsPropsByTargetId, targets, softwareModules);
}); });
} }
private void sendUpdateMessageToTargets(final Map<String, ActionProperties> actionsPropsByTargetId, private void sendUpdateMessageToTargets(
final Map<String, ActionProperties> actionsPropsByTargetId,
final List<Target> targets, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) { final List<Target> targets, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
if (!targets.isEmpty() && isBatchAssignmentsEnabled()) { if (!targets.isEmpty() && isBatchAssignmentsEnabled()) {
sendBatchUpdateMessage(actionsPropsByTargetId, targets, softwareModules); sendBatchUpdateMessage(actionsPropsByTargetId, targets, softwareModules);
} else { } else {
@@ -469,16 +461,15 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
private void sendMultiActionRequestMessages(final String tenant, final List<String> controllerIds) { private void sendMultiActionRequestMessages(final String tenant, final List<String> controllerIds) {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModuleMetadata = new HashMap<>(); final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModuleMetadata = new HashMap<>();
targetManagement.getByControllerID(controllerIds).stream() targetManagement.getByControllerID(controllerIds).stream()
.filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> { .filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> {
final List<Action> activeActions = deploymentManagement final List<Action> activeActions = deploymentManagement
.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT); .findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
activeActions.forEach(action -> action.getDistributionSet().getModules().forEach( activeActions.forEach(action ->
module -> softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata))); action.getDistributionSet().getModules().forEach(module ->
softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata)));
if (!activeActions.isEmpty()) { if (!activeActions.isEmpty()) {
sendMultiActionRequestToTarget(tenant, target, activeActions, sendMultiActionRequestToTarget(tenant, target, activeActions,
@@ -486,10 +477,10 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
.collect(Collectors.toMap(m -> m, softwareModuleMetadata::get))); .collect(Collectors.toMap(m -> m, softwareModuleMetadata::get)));
} }
}); });
} }
private DmfActionRequest createDmfActionRequest(final Target target, final Action action, private DmfActionRequest createDmfActionRequest(
final Target target, final Action action,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
if (action.isCancelingOrCanceled()) { if (action.isCancelingOrCanceled()) {
return createPlainActionRequest(action); return createPlainActionRequest(action);
@@ -499,9 +490,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return createDownloadAndUpdateRequest(target, action.getId(), softwareModules); return createDownloadAndUpdateRequest(target, action.getId(), softwareModules);
} }
private void sendSingleUpdateMessage(final ActionProperties action, final Target target, private void sendSingleUpdateMessage(
final ActionProperties action, final Target target,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
final String tenant = action.getTenant(); final String tenant = action.getTenant();
final URI targetAddress = target.getAddress(); final URI targetAddress = target.getAddress();
@@ -518,7 +509,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
request = createDownloadAndUpdateRequest(target, action.getId(), modules); request = createDownloadAndUpdateRequest(target, action.getId(), modules);
} }
final Message message = getMessageConverter().toMessage(request, final Message message = getMessageConverter().toMessage(
request,
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), getEventTypeForTarget(action))); createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), getEventTypeForTarget(action)));
amqpSenderService.sendMessage(message, targetAddress); amqpSenderService.sendMessage(message, targetAddress);
} }
@@ -528,8 +520,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return; return;
} }
final Message message = new Message("".getBytes(), final Message message = new Message("".getBytes(), createConnectorMessagePropertiesDeleteThing(tenant, controllerId));
createConnectorMessagePropertiesDeleteThing(tenant, controllerId));
amqpSenderService.sendMessage(message, URI.create(targetAddress)); amqpSenderService.sendMessage(message, URI.create(targetAddress));
} }
@@ -545,19 +536,20 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return deploymentManagement.hasPendingCancellations(targetId); return deploymentManagement.hasPendingCancellations(targetId);
} }
private void sendUpdateAttributesMessageToTarget(final String tenant, final String controllerId, private void sendUpdateAttributesMessageToTarget(final String tenant, final String controllerId, final String targetAddress) {
final String targetAddress) {
if (!hasValidAddress(targetAddress)) { if (!hasValidAddress(targetAddress)) {
return; return;
} }
final Message message = new Message("".getBytes(), final Message message = new Message(
"".getBytes(),
createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.REQUEST_ATTRIBUTES_UPDATE)); createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.REQUEST_ATTRIBUTES_UPDATE));
amqpSenderService.sendMessage(message, URI.create(targetAddress)); amqpSenderService.sendMessage(message, URI.create(targetAddress));
} }
private DmfSoftwareModule convertToAmqpSoftwareModule(final Target target, private DmfSoftwareModule convertToAmqpSoftwareModule(
final Target target,
final Entry<SoftwareModule, List<SoftwareModuleMetadata>> entry) { final Entry<SoftwareModule, List<SoftwareModuleMetadata>> entry) {
final DmfSoftwareModule amqpSoftwareModule = new DmfSoftwareModule(); final DmfSoftwareModule amqpSoftwareModule = new DmfSoftwareModule();
amqpSoftwareModule.setModuleId(entry.getKey().getId()); amqpSoftwareModule.setModuleId(entry.getKey().getId());
@@ -596,7 +588,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(), new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
localArtifact.getId(), localArtifact.getSha1Hash())), localArtifact.getId(), localArtifact.getSha1Hash())),
ApiType.DMF) ApiType.DMF)
.stream().collect(Collectors.toMap(ArtifactUrl::getProtocol, ArtifactUrl::getRef))); .stream()
.collect(Collectors.toMap(ArtifactUrl::getProtocol, ArtifactUrl::getRef)));
artifact.setFilename(localArtifact.getFilename()); artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new DmfArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash())); artifact.setHashes(new DmfArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));
@@ -605,8 +598,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return artifact; return artifact;
} }
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata( private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) {
final DistributionSet distributionSet) {
return distributionSet.getModules().stream().collect(Collectors.toMap(m -> m, this::getSoftwareModuleMetadata)); return distributionSet.getModules().stream().collect(Collectors.toMap(m -> m, this::getSoftwareModuleMetadata));
} }
@@ -615,11 +607,14 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId()).getContent(); PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId()).getContent();
} }
private void sendBatchUpdateMessage(final Map<String, ActionProperties> actions, final List<Target> targets, private void sendBatchUpdateMessage(
final Map<String, ActionProperties> actions, final List<Target> targets,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
final List<DmfTarget> dmfTargets = targets.stream().filter(target -> IpUtil.isAmqpUri(target.getAddress())) final List<DmfTarget> dmfTargets = targets.stream()
.map(t -> convertToDmfTarget(t, actions.get(t.getControllerId()).getId())).collect(Collectors.toList()); .filter(target -> IpUtil.isAmqpUri(target.getAddress()))
.map(t -> convertToDmfTarget(t, actions.get(t.getControllerId()).getId()))
.collect(Collectors.toList());
final DmfBatchDownloadAndUpdateRequest batchRequest = new DmfBatchDownloadAndUpdateRequest(); final DmfBatchDownloadAndUpdateRequest batchRequest = new DmfBatchDownloadAndUpdateRequest();
batchRequest.setTimestamp(System.currentTimeMillis()); batchRequest.setTimestamp(System.currentTimeMillis());
@@ -630,15 +625,15 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
// target-specific urls // target-specific urls
final Target firstTarget = targets.get(0); final Target firstTarget = targets.get(0);
if (modules != null) { if (modules != null) {
modules.entrySet() modules.entrySet().forEach(entry ->
.forEach(entry -> batchRequest.addSoftwareModule(convertToAmqpSoftwareModule(firstTarget, entry))); batchRequest.addSoftwareModule(convertToAmqpSoftwareModule(firstTarget, entry)));
} }
// we use only the first action when constructing message as Tenant and // we use only the first action when constructing message as Tenant and action type are the same
// action type are the same
// since all actions have the same trigger // since all actions have the same trigger
final ActionProperties firstAction = actions.values().iterator().next(); final ActionProperties firstAction = actions.values().iterator().next();
final Message message = getMessageConverter().toMessage(batchRequest, final Message message = getMessageConverter().toMessage(
batchRequest,
createMessagePropertiesBatch(firstAction.getTenant(), getBatchEventTopicForAction(firstAction))); createMessagePropertiesBatch(firstAction.getTenant(), getBatchEventTopicForAction(firstAction)));
amqpSenderService.sendMessage(message, firstTarget.getAddress()); amqpSenderService.sendMessage(message, firstTarget.getAddress());
} }

View File

@@ -33,7 +33,6 @@ import org.eclipse.hawkbit.dmf.json.model.DmfAutoConfirmation;
import org.eclipse.hawkbit.dmf.json.model.DmfCreateThing; import org.eclipse.hawkbit.dmf.json.model.DmfCreateThing;
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode; import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ConfirmationManagement; import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
@@ -51,6 +50,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
@@ -63,6 +63,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -75,6 +76,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private static final String THING_ID_NULL = "ThingId is null"; private static final String THING_ID_NULL = "ThingId is null";
private static final String EMPTY_MESSAGE_BODY = "\"\""; private static final String EMPTY_MESSAGE_BODY = "\"\"";
private final AmqpMessageDispatcherService amqpMessageDispatcherService; private final AmqpMessageDispatcherService amqpMessageDispatcherService;
private final ConfirmationManagement confirmationManagement; private final ConfirmationManagement confirmationManagement;
private final EntityFactory entityFactory; private final EntityFactory entityFactory;
@@ -117,14 +119,15 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
* @return a message if <null> no message is send back to sender * @return a message if <null> no message is send back to sender
*/ */
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory") @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
public Message onMessage(final Message message, public Message onMessage(
final Message message,
@Header(name = MessageHeaderKey.TYPE, required = false) final String type, @Header(name = MessageHeaderKey.TYPE, required = false) final String type,
@Header(name = MessageHeaderKey.TENANT, required = false) final String tenant) { @Header(name = MessageHeaderKey.TENANT, required = false) final String tenant) {
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost()); return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
} }
/** /**
* * Executed if a amqp message arrives. * Executed if a amqp message arrives.
* *
* @param message the message * @param message the message
* @param type the type * @param type the type
@@ -133,7 +136,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
* @return the rpc message back to supplier. * @return the rpc message back to supplier.
*/ */
public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) { public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) {
if (StringUtils.isEmpty(type) || StringUtils.isEmpty(tenant)) { if (ObjectUtils.isEmpty(type) || ObjectUtils.isEmpty(tenant)) {
throw new AmqpRejectAndDontRequeueException("Invalid message! tenant and type header are mandatory!"); throw new AmqpRejectAndDontRequeueException("Invalid message! tenant and type header are mandatory!");
} }
@@ -141,27 +144,32 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
try { try {
final MessageType messageType = MessageType.valueOf(type); final MessageType messageType = MessageType.valueOf(type);
switch (messageType) { switch (messageType) {
case THING_CREATED: case THING_CREATED: {
setTenantSecurityContext(tenant); setTenantSecurityContext(tenant);
registerTarget(message, virtualHost); registerTarget(message, virtualHost);
break; break;
case THING_REMOVED: }
case THING_REMOVED: {
setTenantSecurityContext(tenant); setTenantSecurityContext(tenant);
deleteTarget(message); deleteTarget(message);
break; break;
case EVENT: }
case EVENT: {
checkContentTypeJson(message); checkContentTypeJson(message);
setTenantSecurityContext(tenant); setTenantSecurityContext(tenant);
handleIncomingEvent(message); handleIncomingEvent(message);
break; break;
case PING: }
case PING: {
if (isCorrelationIdNotEmpty(message)) { if (isCorrelationIdNotEmpty(message)) {
amqpMessageDispatcherService.sendPingReponseToDmfReceiver(message, tenant, virtualHost); amqpMessageDispatcherService.sendPingResponseToDmfReceiver(message, tenant, virtualHost);
} }
break; break;
default: }
default: {
logAndThrowMessageError(message, "No handle method was found for the given message type."); logAndThrowMessageError(message, "No handle method was found for the given message type.");
} }
}
} catch (AssignmentQuotaExceededException ex) { } catch (AssignmentQuotaExceededException ex) {
throw new AmqpRejectAndDontRequeueException("Could not handle message due to quota violation!", ex); throw new AmqpRejectAndDontRequeueException("Could not handle message due to quota violation!", ex);
} catch (final IllegalArgumentException ex) { } catch (final IllegalArgumentException ex) {
@@ -212,40 +220,51 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final Action action) { final Action action) {
Status status = null; Status status = null;
switch (actionUpdateStatus.getActionStatus()) { switch (actionUpdateStatus.getActionStatus()) {
case DOWNLOAD: case DOWNLOAD: {
status = Status.DOWNLOAD; status = Status.DOWNLOAD;
break; break;
case RETRIEVED: }
case RETRIEVED: {
status = Status.RETRIEVED; status = Status.RETRIEVED;
break; break;
}
case RUNNING: case RUNNING:
case CONFIRMED: case CONFIRMED: {
status = Status.RUNNING; status = Status.RUNNING;
break; break;
case CANCELED: }
case CANCELED: {
status = Status.CANCELED; status = Status.CANCELED;
break; break;
case FINISHED: }
case FINISHED: {
status = Status.FINISHED; status = Status.FINISHED;
break; break;
case ERROR: }
case ERROR: {
status = Status.ERROR; status = Status.ERROR;
break; break;
case WARNING: }
case WARNING: {
status = Status.WARNING; status = Status.WARNING;
break; break;
case DOWNLOADED: }
case DOWNLOADED: {
status = Status.DOWNLOADED; status = Status.DOWNLOADED;
break; break;
case CANCEL_REJECTED: }
case CANCEL_REJECTED: {
status = handleCancelRejectedState(message, action); status = handleCancelRejectedState(message, action);
break; break;
case DENIED: }
case DENIED: {
status = Status.WAIT_FOR_CONFIRMATION; status = Status.WAIT_FOR_CONFIRMATION;
break; break;
default: }
default: {
logAndThrowMessageError(message, "Status for action does not exisit."); logAndThrowMessageError(message, "Status for action does not exisit.");
} }
}
return status; return status;
} }
@@ -254,7 +273,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
if (action.isCancelingOrCanceled()) { if (action.isCancelingOrCanceled()) {
return Status.CANCEL_REJECTED; return Status.CANCEL_REJECTED;
} }
logAndThrowMessageError(message, logAndThrowMessageError(
message,
"Cancel rejected message is not allowed, if action is on state: " + action.getStatus()); "Cancel rejected message is not allowed, if action is on state: " + action.getStatus());
return null; return null;
} }
@@ -274,15 +294,14 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
* Method to create a new target or to find the target if it already exists * Method to create a new target or to find the target if it already exists
* and update its poll time, status and optionally its name and attributes. * and update its poll time, status and optionally its name and attributes.
* *
* @param message the message that contains replyTo property and optionally the * @param message the message that contains replyTo property and optionally the name and attributes in body
* name and attributes in body
* @param virtualHost the virtual host * @param virtualHost the virtual host
*/ */
private void registerTarget(final Message message, final String virtualHost) { private void registerTarget(final Message message, final String virtualHost) {
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL); final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
final String replyTo = message.getMessageProperties().getReplyTo(); final String replyTo = message.getMessageProperties().getReplyTo();
if (StringUtils.isEmpty(replyTo)) { if (ObjectUtils.isEmpty(replyTo)) {
logAndThrowMessageError(message, "No ReplyTo was set for the createThing message."); logAndThrowMessageError(message, "No ReplyTo was set for the createThing message.");
} }
@@ -297,22 +316,22 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final DmfCreateThing thingCreateBody = convertMessage(message, DmfCreateThing.class); final DmfCreateThing thingCreateBody = convertMessage(message, DmfCreateThing.class);
final DmfAttributeUpdate thingAttributeUpdateBody = thingCreateBody.getAttributeUpdate(); final DmfAttributeUpdate thingAttributeUpdateBody = thingCreateBody.getAttributeUpdate();
log.debug("Received \"THING_CREATED\" AMQP message for thing \"{}\" with target name \"{}\" and type " + log.debug(
"\"{}\".", thingId, thingCreateBody.getName(), thingCreateBody.getType()); "Received \"THING_CREATED\" AMQP message for thing \"{}\" with target name \"{}\" and type \"{}\".",
thingId, thingCreateBody.getName(), thingCreateBody.getType());
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri, target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(
thingCreateBody.getName(), thingCreateBody.getType()); thingId, amqpUri, thingCreateBody.getName(), thingCreateBody.getType());
if (thingAttributeUpdateBody != null) { if (thingAttributeUpdateBody != null) {
controllerManagement.updateControllerAttributes(thingId, thingAttributeUpdateBody.getAttributes(), controllerManagement.updateControllerAttributes(
getUpdateMode(thingAttributeUpdateBody)); thingId, thingAttributeUpdateBody.getAttributes(), getUpdateMode(thingAttributeUpdateBody));
} }
} }
log.debug("Target {} reported online state.", thingId); log.debug("Target {} reported online state.", thingId);
sendUpdateCommandToTarget(target); sendUpdateCommandToTarget(target);
} catch (final EntityAlreadyExistsException e) { } catch (final EntityAlreadyExistsException e) {
throw new AmqpRejectAndDontRequeueException( throw new AmqpRejectAndDontRequeueException("Tried to register previously registered target, message will be ignored!", e);
"Tried to register previously registered target, message will be ignored!", e);
} }
} }
@@ -325,23 +344,21 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
} }
private void sendCurrentActionsAsMultiActionToTarget(final Target target) { private void sendCurrentActionsAsMultiActionToTarget(final Target target) {
final List<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), final List<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
MAX_ACTION_COUNT);
final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet) final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet).collect(Collectors.toSet());
.collect(Collectors.toSet());
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet = distributionSets final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet = distributionSets
.stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata)); .stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata));
amqpMessageDispatcherService.sendMultiActionRequestToTarget(target.getTenant(), target, actions, amqpMessageDispatcherService.sendMultiActionRequestToTarget(
target.getTenant(), target, actions,
action -> softwareModulesPerDistributionSet.get(action.getDistributionSet().getId())); action -> softwareModulesPerDistributionSet.get(action.getDistributionSet().getId()));
} }
private void sendOldestActionToTarget(final Target target) { private void sendOldestActionToTarget(final Target target) {
final Optional<Action> actionOptional = controllerManagement final Optional<Action> actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId());
.findActiveActionWithHighestWeight(target.getControllerId());
if (!actionOptional.isPresent()) { if (actionOptional.isEmpty()) {
return; return;
} }
@@ -355,8 +372,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
} }
} }
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata( private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) {
final DistributionSet distributionSet) {
final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId) final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -375,20 +391,23 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
*/ */
private void handleIncomingEvent(final Message message) { private void handleIncomingEvent(final Message message) {
switch (EventTopic.valueOf(getStringHeaderKey(message, MessageHeaderKey.TOPIC, "EventTopic is null"))) { switch (EventTopic.valueOf(getStringHeaderKey(message, MessageHeaderKey.TOPIC, "EventTopic is null"))) {
case UPDATE_ACTION_STATUS: case UPDATE_ACTION_STATUS: {
updateActionStatus(message); updateActionStatus(message);
break; break;
case UPDATE_ATTRIBUTES: }
case UPDATE_ATTRIBUTES: {
updateAttributes(message); updateAttributes(message);
break; break;
case UPDATE_AUTO_CONFIRM: }
case UPDATE_AUTO_CONFIRM: {
setAutoConfirmationState(message); setAutoConfirmationState(message);
break; break;
default: }
default: {
logAndThrowMessageError(message, "Got event without appropriate topic."); logAndThrowMessageError(message, "Got event without appropriate topic.");
break; break;
} }
}
} }
private void deleteTarget(final Message message) { private void deleteTarget(final Message message) {
@@ -400,8 +419,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final DmfAttributeUpdate attributeUpdate = convertMessage(message, DmfAttributeUpdate.class); final DmfAttributeUpdate attributeUpdate = convertMessage(message, DmfAttributeUpdate.class);
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL); final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(), controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(), getUpdateMode(attributeUpdate));
getUpdateMode(attributeUpdate));
} }
private void setAutoConfirmationState(final Message message) { private void setAutoConfirmationState(final Message message) {
@@ -439,22 +457,19 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final Status status = mapStatus(message, actionUpdateStatus, action); final Status status = mapStatus(message, actionUpdateStatus, action);
final Action updatedAction; final Action updatedAction;
if (actionUpdateStatus.getActionStatus() == DmfActionStatus.CONFIRMED) { if (actionUpdateStatus.getActionStatus() == DmfActionStatus.CONFIRMED) {
updatedAction = confirmationManagement.confirmAction(action.getId(), updatedAction = confirmationManagement.confirmAction(action.getId(),
actionUpdateStatus.getCode().orElse(null), messages); actionUpdateStatus.getCode().orElse(null), messages);
} else if (actionUpdateStatus.getActionStatus() == DmfActionStatus.DENIED) { } else if (actionUpdateStatus.getActionStatus() == DmfActionStatus.DENIED) {
updatedAction = confirmationManagement.denyAction(action.getId(), actionUpdateStatus.getCode().orElse(null), updatedAction = confirmationManagement.denyAction(action.getId(), actionUpdateStatus.getCode().orElse(null), messages);
messages);
} else { } else {
final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status) final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status).messages(messages);
.messages(messages);
actionUpdateStatus.getCode().ifPresent(code -> { actionUpdateStatus.getCode().ifPresent(code -> {
actionStatus.code(code); actionStatus.code(code);
actionStatus.message("Device reported status code: " + code); actionStatus.message("Device reported status code: " + code);
}); });
updatedAction = ((Status.CANCELED == status) || (Status.CANCEL_REJECTED == status)) ? updatedAction = ((Status.CANCELED == status) || (Status.CANCEL_REJECTED == status))
controllerManagement.addCancelActionStatus(actionStatus) ? controllerManagement.addCancelActionStatus(actionStatus)
: controllerManagement.addUpdateActionStatus(actionStatus); : controllerManagement.addUpdateActionStatus(actionStatus);
} }
@@ -463,8 +478,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
} }
} }
// Exception squid:S3655 - logAndThrowMessageError throws exception, i.e. // Exception squid:S3655 - logAndThrowMessageError throws exception, i.e. get will not be called
// get will not be called
@SuppressWarnings("squid:S3655") @SuppressWarnings("squid:S3655")
private Action checkActionExist(final Message message, final DmfActionUpdateStatus actionUpdateStatus) { private Action checkActionExist(final Message message, final DmfActionUpdateStatus actionUpdateStatus) {
final Long actionId = actionUpdateStatus.getActionId(); final Long actionId = actionUpdateStatus.getActionId();
@@ -473,9 +487,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
actionUpdateStatus.getActionStatus()); actionUpdateStatus.getActionStatus());
final Optional<Action> findActionWithDetails = controllerManagement.findActionWithDetails(actionId); final Optional<Action> findActionWithDetails = controllerManagement.findActionWithDetails(actionId);
if (!findActionWithDetails.isPresent()) { if (findActionWithDetails.isEmpty()) {
logAndThrowMessageError(message, logAndThrowMessageError(message, "Got intermediate notification about action " + actionId + " but action does not exist");
"Got intermediate notification about action " + actionId + " but action does not exist");
} }
return findActionWithDetails.get(); return findActionWithDetails.get();
@@ -486,7 +499,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
} }
private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) { private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {
return systemSecurityContext return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
} }
} }

View File

@@ -23,7 +23,7 @@ public interface AmqpMessageSenderService {
/** /**
* Send the given message to the given uri. The uri contains the (virtual) * Send the given message to the given uri. The uri contains the (virtual)
* host and exchange e.g amqp://host/exchange. * host and exchange e.g. amqp://host/exchange.
* *
* @param message the amqp message * @param message the amqp message
* @param replyTo the reply to uri * @param replyTo the reply to uri

View File

@@ -21,7 +21,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class AmqpProperties { public class AmqpProperties {
private static final int DEFAULT_QUEUE_DECLARATION_RETRIES = 50; private static final int DEFAULT_QUEUE_DECLARATION_RETRIES = 50;
private static final long DEFAULT_REQUEUE_DELAY = 0; private static final long DEFAULT_REQUEUE_DELAY = 0;
/** /**

View File

@@ -50,8 +50,7 @@ public class BaseAmqpService {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T convertMessage(@NotNull final Message message, final Class<T> clazz) { public <T> T convertMessage(@NotNull final Message message, final Class<T> clazz) {
checkMessageBody(message); checkMessageBody(message);
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, clazz.getName());
clazz.getName());
return (T) rabbitTemplate.getMessageConverter().fromMessage(message); return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
} }
@@ -104,5 +103,4 @@ public class BaseAmqpService {
protected void cleanMessageHeaderProperties(final Message message) { protected void cleanMessageHeaderProperties(final Message message) {
message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME); message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
} }
} }

View File

@@ -31,20 +31,19 @@ public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitList
* @param errorHandler the error handler which should be use * @param errorHandler the error handler which should be use
* @see SimpleMessageListenerContainer#setMissingQueuesFatal * @see SimpleMessageListenerContainer#setMissingQueuesFatal
*/ */
public ConfigurableRabbitListenerContainerFactory(final boolean missingQueuesFatal, final int declarationRetries, public ConfigurableRabbitListenerContainerFactory(
final ErrorHandler errorHandler) { final boolean missingQueuesFatal, final int declarationRetries, final ErrorHandler errorHandler) {
this.declarationRetries = declarationRetries; this.declarationRetries = declarationRetries;
setErrorHandler(errorHandler); setErrorHandler(errorHandler);
setMissingQueuesFatal(missingQueuesFatal); setMissingQueuesFatal(missingQueuesFatal);
} }
@Override
// Exception squid:UnusedProtectedMethod - called by // Exception squid:UnusedProtectedMethod - called by
// AbstractRabbitListenerContainerFactory // AbstractRabbitListenerContainerFactory
@SuppressWarnings("squid:UnusedProtectedMethod") @SuppressWarnings("squid:UnusedProtectedMethod")
protected void initializeContainer(final SimpleMessageListenerContainer instance, @Override
final RabbitListenerEndpoint endpoint) { protected void initializeContainer(final SimpleMessageListenerContainer instance, final RabbitListenerEndpoint endpoint) {
super.initializeContainer(instance, endpoint); super.initializeContainer(instance, endpoint);
instance.setDeclarationRetries(declarationRetries); instance.setDeclarationRetries(declarationRetries);
} }

View File

@@ -61,5 +61,4 @@ public class DefaultAmqpMessageSenderService extends BaseAmqpService implements
protected static boolean isCorrelationIdEmpty(final Message message) { protected static boolean isCorrelationIdEmpty(final Message message) {
return !StringUtils.hasLength(message.getMessageProperties().getCorrelationId()); return !StringUtils.hasLength(message.getMessageProperties().getCorrelationId());
} }
} }

View File

@@ -86,7 +86,8 @@ public class DelayedRequeueExceptionStrategy extends ConditionalRejectingErrorHa
} }
private static boolean isMessageException(final Throwable cause) { private static boolean isMessageException(final Throwable cause) {
return cause instanceof InvalidTargetAddressException || cause instanceof MessageConversionException return cause instanceof InvalidTargetAddressException ||
|| cause instanceof MessageHandlingException; cause instanceof MessageConversionException ||
cause instanceof MessageHandlingException;
} }
} }

View File

@@ -19,6 +19,4 @@ import org.springframework.context.annotation.Import;
@Configuration @Configuration
@ComponentScan @ComponentScan
@Import(AmqpConfiguration.class) @Import(AmqpConfiguration.class)
public class DmfApiConfiguration { public class DmfApiConfiguration {}
}