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 <ondrej.charvat@proton.me>
This commit is contained in:
charvadzo
2024-01-22 14:01:00 +01:00
committed by GitHub
parent af56b71d53
commit 49a5509e89
8 changed files with 236 additions and 24 deletions

View File

@@ -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(),

View File

@@ -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<String> targetNameCaptor;
@Captor
private ArgumentCaptor<String> targetTypeNameCaptor;
@Captor
private ArgumentCaptor<URI> 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() {

View File

@@ -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.
* <p>
* If the specified repository software modules are <code>null</code> then
* the created matcher will only match if the JSON software modules are

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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<JpaTarget> spec = (targetRoot, query, cb) -> cb
.equal(targetRoot.get(JpaTarget_.controllerId), controllerId);
final String name, final String type) {
final Specification<JpaTarget> 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<TargetType> 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;
}

View File

@@ -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

View File

@@ -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.<br />
<br />
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.<br />
<br />
The "attributeUpdate" property provides the attributes of the thing, for details see UPDATE_ATTRIBUTES message. This property is optional.