From 49a5509e89c00aab13bc236a5b631291c84bec15 Mon Sep 17 00:00:00 2001 From: charvadzo <120425386+charvadzo@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:01:00 +0100 Subject: [PATCH] Enable specifying target type when created using DMF API (#1472) Extension of DMF API with possibility of setting target type name when creating target. If a target type with the provided name is found (was created beforehand) then it is associated with the new target. Signed-off-by: Ondrej Charvat --- .../amqp/AmqpMessageHandlerService.java | 6 +- .../amqp/AmqpMessageHandlerServiceTest.java | 28 +++- .../matcher/SoftwareModuleJsonMatcher.java | 2 +- .../dmf/json/model/DmfCreateThing.java | 11 ++ .../repository/ControllerManagement.java | 8 +- .../management/JpaControllerManagement.java | 70 ++++++++-- .../management/ControllerManagementTest.java | 122 +++++++++++++++++- site/content/apis/dmf_api.md | 13 ++ 8 files changed, 236 insertions(+), 24 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 80ca28762..ad5c33d55 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -231,14 +231,18 @@ public class AmqpMessageHandlerService extends BaseAmqpService { final URI amqpUri = IpUtil.createAmqpUri(virtualHost, replyTo); final Target target; if (isOptionalMessageBodyEmpty(message)) { + LOG.debug("Received \"THING_CREATED\" AMQP message for thing \"{}\" without body.", thingId); target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri); } else { checkContentTypeJson(message); final DmfCreateThing thingCreateBody = convertMessage(message, DmfCreateThing.class); final DmfAttributeUpdate thingAttributeUpdateBody = thingCreateBody.getAttributeUpdate(); + LOG.debug("Received \"THING_CREATED\" AMQP message for thing \"{}\" with target name \"{}\" and type " + + "\"{}\".", thingId, thingCreateBody.getName(), thingCreateBody.getType()); + target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri, - thingCreateBody.getName()); + thingCreateBody.getName(), thingCreateBody.getType()); if (thingAttributeUpdateBody != null) { controllerManagement.updateControllerAttributes(thingId, thingAttributeUpdateBody.getAttributes(), 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 ae86ace38..c6c3009e1 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 @@ -50,7 +50,6 @@ import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; -import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; @@ -167,6 +166,9 @@ public class AmqpMessageHandlerServiceTest { @Captor private ArgumentCaptor targetNameCaptor; + @Captor + private ArgumentCaptor targetTypeNameCaptor; + @Captor private ArgumentCaptor uriCaptor; @@ -228,7 +230,8 @@ public class AmqpMessageHandlerServiceTest { uriCaptor.capture())).thenReturn(targetMock); } else { when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(), - uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock); + uriCaptor.capture(), targetNameCaptor.capture(), targetTypeNameCaptor.capture())) + .thenReturn(targetMock); if (payload.getAttributeUpdate() != null) { when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture(), modeCaptor.capture())).thenReturn(null); @@ -280,6 +283,27 @@ public class AmqpMessageHandlerServiceTest { assertThat(targetNameCaptor.getValue()).as("Thing name is wrong").isEqualTo(thingName); } + @Test + @Description("Tests the creation of a target/thing with specified type name by calling the same method that incoming RabbitMQ messages would access.") + public void createThingWithType() { + final String knownThingId = "2"; + final String knownThingTypeName = "TargetTypeName"; + + final DmfCreateThing payload = new DmfCreateThing(); + payload.setType(knownThingTypeName); + + processThingCreatedMessage(knownThingId, payload); + + assertThingIdCapturedField(knownThingId); + assertReplyToCapturedField("MyTest"); + assertThingTypeCapturedField(knownThingTypeName); + } + + @Step + private void assertThingTypeCapturedField(final String thingType) { + assertThat(targetTypeNameCaptor.getValue()).as("Thing type is wrong").isEqualTo(thingType); + } + @Test @Description("Tests not allowed body in message") public void createThingWithWrongBody() { diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java index 95d48fc40..9dc5eec4f 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java @@ -26,7 +26,7 @@ public final class SoftwareModuleJsonMatcher { /** * Creates a matcher that matches when the list of repository software - * modules arelogically equal to the specified JSON software modules. + * modules are logically equal to the specified JSON software modules. *

* If the specified repository software modules are null then * the created matcher will only match if the JSON software modules are diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java index c356e7234..c5380bf84 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java @@ -24,6 +24,9 @@ public class DmfCreateThing { @JsonProperty private String name; + @JsonProperty + private String type; + @JsonProperty private DmfAttributeUpdate attributeUpdate; @@ -35,6 +38,14 @@ public class DmfCreateThing { this.name = name; } + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + public DmfAttributeUpdate getAttributeUpdate() { return attributeUpdate; } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 0b8a668df..804b456fa 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -223,7 +223,8 @@ public interface ControllerManagement { /** * Register new target in the repository (plug-and-play) and in case it * already exists updates {@link Target#getAddress()} and - * {@link Target#getLastTargetQuery()} and {@link Target#getName()} and + * {@link Target#getLastTargetQuery()} and {@link Target#getName()} + * and {@link Target#getTargetType()} and * switches if {@link TargetUpdateStatus#UNKNOWN} to * {@link TargetUpdateStatus#REGISTERED}. * @@ -233,10 +234,13 @@ public interface ControllerManagement { * the client IP address of the target, might be {@code null} * @param name * the name of the target + * @param type + * the target type name of the target * @return target reference */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name); + Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name, + String type); /** * Retrieves last {@link Action} for a download of an artifact of given diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index 24e099294..7cb449d38 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.TargetTypeManagement; import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; @@ -84,6 +85,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.security.SystemSecurityContext; @@ -160,6 +162,9 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Autowired private ConfirmationManagement confirmationManagement; + @Autowired + private TargetTypeManagement targetTypeManagement; + public JpaControllerManagement(final ScheduledExecutorService executorService, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties) { @@ -384,28 +389,42 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Transactional(isolation = Isolation.READ_COMMITTED) @Retryable(include = ConcurrencyFailureException.class, exclude = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address) { - return findOrRegisterTargetIfItDoesNotExist(controllerId, address, null); + return findOrRegisterTargetIfItDoesNotExist(controllerId, address, null, null); } @Override @Transactional(isolation = Isolation.READ_COMMITTED) @Retryable(include = ConcurrencyFailureException.class, exclude = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address, - final String name) { - final Specification spec = (targetRoot, query, cb) -> cb - .equal(targetRoot.get(JpaTarget_.controllerId), controllerId); + final String name, final String type) { + final Specification spec = + (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId); - return targetRepository.findOne(spec).map(target -> updateTarget(target, address, name)) - .orElseGet(() -> createTarget(controllerId, address, name)); + return targetRepository.findOne(spec).map(target -> updateTarget(target, address, name, type)) + .orElseGet(() -> createTarget(controllerId, address, name, type)); } - private Target createTarget(final String controllerId, final URI address, final String name) { + private Target createTarget(final String controllerId, final URI address, final String name, final String type) { - final Target result = targetRepository.save((JpaTarget) entityFactory.target().create() + LOG.debug("Creating target for thing ID \"{}\".", controllerId); + JpaTarget jpaTarget = (JpaTarget) entityFactory.target().create() .controllerId(controllerId).description("Plug and Play target: " + controllerId) .name((StringUtils.hasText(name) ? name : controllerId)).status(TargetUpdateStatus.REGISTERED) .lastTargetQuery(System.currentTimeMillis()) - .address(Optional.ofNullable(address).map(URI::toString).orElse(null)).build()); + .address(Optional.ofNullable(address).map(URI::toString).orElse(null)).build(); + + if (StringUtils.hasText(type)) { + var targetTypeOptional = getTargetType(type); + if (targetTypeOptional.isPresent()) { + LOG.debug("Setting target type for thing ID \"{}\" to \"{}\".", controllerId, type); + jpaTarget.setTargetType(targetTypeOptional.get()); + } else { + LOG.error("Target type with the provided name \"{}\" was not found. Creating target for thing ID" + + " \"{}\" without target type assignment", type, controllerId); + } + } + + final Target result = targetRepository.save(jpaTarget); afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher() .publishEvent(new TargetPollEvent(result, eventPublisherHolder.getApplicationId()))); @@ -413,6 +432,10 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont return result; } + Optional getTargetType(String targetTypeName) { + return systemSecurityContext.runAsSystem(() -> targetTypeManagement.getByName(targetTypeName)); + } + /** * Flush the update queue by means to persisting * {@link Target#getLastTargetQuery()}. @@ -501,14 +524,30 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont * or the buffer queue is full. * */ - private Target updateTarget(final JpaTarget toUpdate, final URI address, final String name) { - if (isStoreEager(toUpdate, address, name) || !queue.offer(new TargetPoll(toUpdate))) { + private Target updateTarget(final JpaTarget toUpdate, final URI address, final String name, final String type) { + if (isStoreEager(toUpdate, address, name, type) || !queue.offer(new TargetPoll(toUpdate))) { if (isAddressChanged(toUpdate.getAddress(), address)) { toUpdate.setAddress(address.toString()); } if (isNameChanged(toUpdate.getName(), name)) { toUpdate.setName(name); } + + if (isTypeChanged(toUpdate.getTargetType(), type)) { + if (StringUtils.hasText(type)) { + var targetTypeOptional = getTargetType(type); + if (targetTypeOptional.isPresent()) { + LOG.debug("Updating target type for thing ID \"{}\" to \"{}\".", toUpdate.getControllerId(), type); + toUpdate.setTargetType(targetTypeOptional.get()); + } else { + LOG.error("Target type with the provided name \"{}\" was not found. Target type for thing ID" + + " \"{}\" will not be updated", type, toUpdate.getControllerId()); + } + } else { + LOG.debug("Removing target type assignment for thing ID \"{}\".", toUpdate.getControllerId()); + toUpdate.setTargetType(null); //unassign target type if "" target type name was provided + } + } if (isStatusUnknown(toUpdate.getUpdateStatus())) { toUpdate.setUpdateStatus(TargetUpdateStatus.REGISTERED); } @@ -520,9 +559,10 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont return toUpdate; } - private boolean isStoreEager(final JpaTarget toUpdate, final URI address, final String name) { + private boolean isStoreEager(final JpaTarget toUpdate, final URI address, final String name, final String type) { return repositoryProperties.isEagerPollPersistence() || isAddressChanged(toUpdate.getAddress(), address) - || isNameChanged(toUpdate.getName(), name) || isStatusUnknown(toUpdate.getUpdateStatus()); + || isNameChanged(toUpdate.getName(), name) || isTypeChanged(toUpdate.getTargetType(), type) + || isStatusUnknown(toUpdate.getUpdateStatus()); } private static boolean isAddressChanged(final URI addressToUpdate, final URI address) { @@ -533,6 +573,10 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont return StringUtils.hasText(name) && !nameToUpdate.equals(name); } + private static boolean isTypeChanged(final TargetType targetTypeToUpdate, final String type) { + return (type != null) && (targetTypeToUpdate == null || !targetTypeToUpdate.getName().equals(type)); + } + private static boolean isStatusUnknown(final TargetUpdateStatus statusToUpdate) { return TargetUpdateStatus.UNKNOWN == statusToUpdate; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java index 1dcf65382..ab167c893 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE; import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS; import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY; @@ -52,6 +53,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; @@ -77,6 +79,8 @@ import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.util.TargetTestData; import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch; +import org.eclipse.hawkbit.repository.test.util.WithUser; + import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -510,6 +514,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Register a controller which does not exist") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) void findOrRegisterTargetIfItDoesNotExist() { @@ -523,15 +528,122 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Register a controller with name which does not exist and update its name") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 1) }) void findOrRegisterTargetIfItDoesNotExistWithName() { - final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, "TestName"); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, "TestName", null); final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, - "ChangedTestName"); + "ChangedTestName", null); assertThat(target.getId()).as("Target should be the equals").isEqualTo(sameTarget.getId()); - assertThat(target.getName()).as("Taget names should be different").isNotEqualTo(sameTarget.getName()); - assertThat(sameTarget.getName()).as("Taget name should be changed").isEqualTo("ChangedTestName"); + assertThat(target.getName()).as("Target names should be different").isNotEqualTo(sameTarget.getName()); + assertThat(sameTarget.getName()).as("Target name should be changed").isEqualTo("ChangedTestName"); + assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); + } + + @Test + @Description("Register a controller which does not exist with existing target type and update its target type to another existing one") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) + @ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class, count = 2), + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 1) }) + void findOrRegisterTargetIfItDoesNotExistWithExistingTypeAndUpdateToExistingType() { + createTargetType("knownTargetTypeName1"); + createTargetType("knownTargetTypeName2"); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, "knownTargetTypeName1"); + final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, "knownTargetTypeName2"); + assertThat(target.getId()).as("Target should be the same").isEqualTo(sameTarget.getId()); + assertThat(target.getTargetType().getName()).as("Target type should be set") + .isEqualTo("knownTargetTypeName1"); + assertThat(sameTarget.getTargetType().getName()).as("Target type should be changed") + .isEqualTo("knownTargetTypeName2"); + assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); + } + + private void createTargetType(String targetTypeName) { + systemSecurityContext.runAsSystem(() -> + targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName))); + } + + @Test + @Description("Register a controller which does not exist with existing target type and update its target type to non existing one") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) + @ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 2) }) + void findOrRegisterTargetIfItDoesNotExistWithExistingTypeAndUpdateToNonExistingType() { + createTargetType("knownTargetTypeName"); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null,"knownTargetTypeName"); + final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null,"unknownTargetTypeName"); + assertThat(target.getId()).as("Target should be the same").isEqualTo(sameTarget.getId()); + assertThat(sameTarget.getTargetType().getName()).as("Target type should be unchanged") + .isEqualTo("knownTargetTypeName"); + assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); + } + + @Test + @Description("Register a controller which does not exist with existing target type and unassign its target type") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) + @ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 1) }) + void findOrRegisterTargetIfItDoesNotExistWithExistingTypeAndUnassignType() { + createTargetType("knownTargetTypeName"); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, "knownTargetTypeName"); + final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, ""); + assertThat(target.getId()).as("Target should be the same").isEqualTo(sameTarget.getId()); + assertThat(sameTarget.getTargetType()).as("Target type should be unassigned") + .isNull(); + assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); + } + + @Test + @Description("Register a controller which does not exist without target type and update its target type to existing one") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) + @ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 1) }) + void findOrRegisterTargetIfItDoesNotExistWithoutTypeAndUpdateToExistingType() { + createTargetType("knownTargetTypeName"); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, null); + final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, "knownTargetTypeName"); + assertThat(target.getId()).as("Target should be the equals").isEqualTo(sameTarget.getId()); + assertThat(target.getTargetType()).as("Target type should not be assigned") + .isNull(); + assertThat(sameTarget.getTargetType().getName()).as("Target type should be assigned") + .isEqualTo("knownTargetTypeName"); + assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); + } + + @Test + @Description("Register a controller which does not exist with non existing target type and update its target type to existing one") + @WithUser(principal = "controller", authorities = { CONTROLLER_ROLE }) + @ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 1) }) + void findOrRegisterTargetIfItDoesNotExistWithNonExistingTypeAndUpdateToExistingType() { + createTargetType("knownTargetTypeName"); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, "unknownTargetTypeName"); + final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, + null, "knownTargetTypeName"); + assertThat(target.getId()).as("Target should be the equals").isEqualTo(sameTarget.getId()); + assertThat(target.getTargetType()).as("Target type should not be assigned") + .isNull(); + assertThat(sameTarget.getTargetType().getName()).as("Target type should be assigned") + .isEqualTo("knownTargetTypeName"); assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); } @@ -616,7 +728,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { assertThat(newTarget.getName()).isEqualTo(controllerId); final Target firstTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, - LOCALHOST, targetName); + LOCALHOST, targetName, null); assertThat(firstTimeUpdatedTarget.getName()).isEqualTo(targetName); // Name should not change to default (name=targetId) if target is diff --git a/site/content/apis/dmf_api.md b/site/content/apis/dmf_api.md index 56085b5e1..7cd544f13 100644 --- a/site/content/apis/dmf_api.md +++ b/site/content/apis/dmf_api.md @@ -63,6 +63,7 @@ Payload Template (optional): ```json { "name": "String", + "type": "String", "attributeUpdate": { "attributes": { "exampleKey1" : "exampleValue1", @@ -74,6 +75,18 @@ Payload Template (optional): ``` The "name" property specifies the name of the thing, which by default is the thing ID. This property is optional.
+
+The "type" property specifies name of a target type which should be assigned to the created/updated target. The +target type with the specified name should be created in advance, otherwise it can't be assigned to the target, +resulting in: +* error is logged +* if the target does not exist then it is created without any target type assigned +* if it exists already then no changes to its target type assignment are made. + +If the "type" property is set to a blank string while updating an existing target then any eventual target type +assignment is removed from the target. This property is optional and if omitted then no changes to the target type +assignment are made.
+
The "attributeUpdate" property provides the attributes of the thing, for details see UPDATE_ATTRIBUTES message. This property is optional.