Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional name to payload of thing created message #888

Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
64b741f
First implementation pushed because of debugging purpose
AmmarBikic Sep 6, 2019
7bda7cc
Add name field and tests regarding name field functionality in THING_…
AmmarBikic Sep 11, 2019
7c78308
SonarQube realted changes in name field functionality in THING_CREATED
AmmarBikic Sep 12, 2019
66d52fb
Add name field and tests regarding name field functionality in UPDATE…
AmmarBikic Sep 12, 2019
22cf51c
Adapt documentation due to name field in THING_CREATED and UPDATE_ATT…
AmmarBikic Sep 16, 2019
158f442
Add integration tests regarding name field functionality in THING_CRE…
AmmarBikic Sep 18, 2019
2d13ca8
Reformat after finding format bug regarding THING_CREATED
AmmarBikic Sep 19, 2019
a843035
Reformat after finding the real format bug regarding THING_CREATED
AmmarBikic Sep 19, 2019
40a7998
Reformat regarding THING_CREATED
AmmarBikic Sep 20, 2019
90a74c2
Use constant in THING_CREATED
AmmarBikic Sep 20, 2019
1d82766
Format in THING_CREATED
AmmarBikic Sep 20, 2019
6178593
Resolving peer review comments regarding THING_CREATED
AmmarBikic Sep 27, 2019
6d5aaca
Resolving peer review comments (organize imports) regarding THING_CRE…
AmmarBikic Sep 27, 2019
6a8ff79
Refactoring regarding THING_CREATED
AmmarBikic Sep 27, 2019
92bced0
Refactoring due to peer review
AmmarBikic Oct 1, 2019
10dad68
Refactoring due to peer review
AmmarBikic Oct 2, 2019
d9e014c
Excluding UPDATE_ATTRIBUTES changes and provide functionality of upda…
AmmarBikic Oct 8, 2019
105fb02
Refactoring due to peer review
AmmarBikic Oct 10, 2019
910c453
Refactoring due to peer review
AmmarBikic Oct 11, 2019
c7ba85c
Fix SonarQube finding
AmmarBikic Oct 11, 2019
6b05759
Merge remote-tracking branch 'origin/master' into feature_add_optiona…
AmmarBikic Oct 14, 2019
a4f4fc9
Merge master into current branch
AmmarBikic Oct 14, 2019
0ef7090
Fix peer review findings
AmmarBikic Oct 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/content/apis/dmf_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,22 @@ Message Properties | Description
content_type | The content type of the payload | String | true
reply_to | Exchange to reply to. The default is sp.direct.exchange which is bound to the sp_direct_queue | String | false

Example headers
Example headers and payload:

Header | MessageProperties
----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------
type=THING\_CREATED <br /> tenant=tenant123 <br /> thingId=abc <br /> sender=Lwm2m | content\_type=application/json <br /> reply_to (optional) =sp.connector.replyTo

Payload Template (optional):

```json
{
"name": "String"
}
```

The "name" property specifies the name of the thing, which per default is the thing ID. This property is optional.
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved

AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
### UPDATE_ATTRIBUTES

Message to update target attributes. This message can be send in response to a _REQUEST_ATTRIBUTES_UPDATE_ event, sent by hawkBit.
Expand Down Expand Up @@ -88,10 +98,12 @@ Payload Template:
"exampleKey1" : "exampleValue1",
"exampleKey2" : "exampleValue2"
},
"name": "String",
"mode": "String"
}
```

The "name" property specifies the name of the thing. This property is optional. <br />
The "mode" property specifies the update mode that should be applied. This property is optional. Possible [mode](https://github.com/eclipse/hawkbit/tree/master/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfUpdateMode.java) values:

Value | Description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.springframework.util.StringUtils.hasText;

import java.io.Serializable;
import java.net.URI;
Expand All @@ -26,6 +27,7 @@
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
import org.eclipse.hawkbit.dmf.json.model.DmfCreateThing;
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
Expand Down Expand Up @@ -62,11 +64,9 @@
import org.springframework.util.StringUtils;

/**
*
* {@link AmqpMessageHandlerService} handles all incoming target interaction
* AMQP messages (e.g. create target, check for updates etc.) for the queue
* which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue.
*
*/
public class AmqpMessageHandlerService extends BaseAmqpService {

Expand All @@ -82,9 +82,11 @@ public class AmqpMessageHandlerService extends BaseAmqpService {

private final SystemSecurityContext systemSecurityContext;

private static final String THING_ID_NULL = "ThingId is null";

/**
* Constructor.
*
*
* @param rabbitTemplate
* for converting messages
* @param amqpMessageDispatcherService
Expand Down Expand Up @@ -120,7 +122,6 @@ public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
* the message type
* @param tenant
* the contentType of the message
*
* @return a message if <null> no message is send back to sender
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory")
Expand All @@ -132,7 +133,7 @@ public Message onMessage(final Message message,

/**
* * Executed if a amqp message arrives.
*
*
* @param message
* the message
* @param type
Expand Down Expand Up @@ -192,27 +193,43 @@ private static void setTenantSecurityContext(final String tenantId) {
}

/**
* Method to create a new target or to find the target if it already exists.
* Method to register a new target.
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
*
* @param targetID
* the ID of the target/thing
* @param ip
* the ip of the target/thing
* @param message
* the message that contains the target/thing
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
* @param virtualHost
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
* the virtual host
*/
private void registerTarget(final Message message, final String virtualHost) {
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
final String replyTo = message.getMessageProperties().getReplyTo();
String name = null;
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
final Target target;

if (StringUtils.isEmpty(replyTo)) {
logAndThrowMessageError(message, "No ReplyTo was set for the createThing message.");
}

try {
final URI amqpUri = IpUtil.createAmqpUri(virtualHost, replyTo);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
if (message.toString().contains("name")) {
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
checkContentTypeJson(message);
// Check whether name property set
final DmfCreateThing targetProperties = convertMessage(message, DmfCreateThing.class);
// Will be true if "name" properly in body and not just contained in some
// attributes
if (hasText(targetProperties.getName())) {
name = targetProperties.getName();
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri, name);
} else {
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
}
} else {
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
}
LOG.debug("Target {} reported online state.", thingId);
sendUpdateCommandToTarget(target);
} catch (EntityAlreadyExistsException e) {
} catch (final EntityAlreadyExistsException e) {
throw new AmqpRejectAndDontRequeueException("Target already registered, message will be ignored!", e);
}
}
Expand Down Expand Up @@ -294,7 +311,12 @@ private void handleIncomingEvent(final Message message) {

private void updateAttributes(final Message message) {
final DmfAttributeUpdate attributeUpdate = convertMessage(message, DmfAttributeUpdate.class);
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);

// If new name provided in body, then change the name
if (hasText(attributeUpdate.getName())) {
controllerManagement.updateControllerName(thingId, attributeUpdate.getName());
}

controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(),
getUpdateMode(attributeUpdate));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
Expand All @@ -14,6 +14,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand All @@ -36,6 +37,7 @@
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.DmfCreateThing;
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
Expand Down Expand Up @@ -135,6 +137,9 @@ public class AmqpMessageHandlerServiceTest {
@Captor
private ArgumentCaptor<UpdateMode> modeCaptor;

@Captor
private ArgumentCaptor<String> targetNameCaptor;

@Before
@SuppressWarnings({ "rawtypes", "unchecked" })
public void before() throws Exception {
Expand Down Expand Up @@ -193,6 +198,62 @@ public void createThing() {

}

@Test
@Description("Tests the creation of a target/thing with specified name by calling the same method that incoming RabbitMQ messages would access.")
public void createThingWithName() {
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
final String knownThingId = "2";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
final DmfCreateThing targetProperties = new DmfCreateThing();
targetProperties.setName("NonDefaultTargetName");

final Message message = messageConverter.toMessage(targetProperties, messageProperties);

final Target targetMock = mock(Target.class);

final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
final ArgumentCaptor<String> targetNameCaptor = ArgumentCaptor.forClass(String.class);

when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock);
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty());

amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");

assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
assertThat(uriCaptor.getValue().toString()).as("Uri is not right").isEqualTo("amqp://vHost/MyTest");
assertThat(targetNameCaptor.getValue()).as("Thing name is not right").isEqualTo(targetProperties.getName());
}

@Test
@Description("Tests the creation of a target/thing without name but with body by calling the same method that incoming RabbitMQ messages would access.")
public void createThingWithBodyWithoutName() {
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
final String knownThingId = "3";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
// put "fake" name in the attributes to also test this behaviour, name
// should still be default (=targetId)
attributeUpdate.getAttributes().put("name", "testValue1");

final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate,
messageProperties);

final Target targetMock = mock(Target.class);

final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
uriCaptor.capture())).thenReturn(targetMock);
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty());

amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");

assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
assertThat(uriCaptor.getValue().toString()).as("Uri is not right").isEqualTo("amqp://vHost/MyTest");
}

@Test
@Description("Tests the target attribute update by calling the same method that incoming RabbitMQ messages would access.")
public void updateAttributes() {
Expand All @@ -219,6 +280,35 @@ public void updateAttributes() {

}

@Test
@Description("Tests the target attribute update with name update by calling the same method that incoming RabbitMQ messages would access.")
public void updateAttributesWithName() {
final String knownThingId = "1";
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
messageProperties.setHeader(MessageHeaderKey.TOPIC, "UPDATE_ATTRIBUTES");
final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
attributeUpdate.getAttributes().put("testKey1", "testValue1");
attributeUpdate.getAttributes().put("testKey2", "testValue2");
attributeUpdate.setName("UpdatedTargetName");

final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate,
messageProperties);

when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture(),
modeCaptor.capture())).thenReturn(null);
doNothing().when(controllerManagementMock).updateControllerName(targetIdCaptor.capture(), targetNameCaptor.capture());

amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");

// verify
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
assertThat(attributesCaptor.getValue()).as("Attributes is not right")
.isEqualTo(attributeUpdate.getAttributes());
assertThat(targetNameCaptor.getValue()).as("Thing name is wrong").isEqualTo(attributeUpdate.getName());

}

@Test
@Description("Verifies that the update mode is retrieved from the UPDATE_ATTRIBUTES message and passed to the controller management.")
public void attributeUpdateModes() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -29,6 +29,9 @@ public class DmfAttributeUpdate {
@JsonProperty
private DmfUpdateMode mode;

@JsonProperty
AmmarBikic marked this conversation as resolved.
Show resolved Hide resolved
private String name;

public DmfUpdateMode getMode() {
return mode;
}
Expand All @@ -41,4 +44,12 @@ public Map<String, String> getAttributes() {
return attributes;
}

public String getName() {
return name;
}

public void setName(final String name) {
this.name = name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.dmf.json.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* JSON representation of the Attribute THING_CREATED message.
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DmfCreateThing {

@JsonProperty
private String name;

public String getName() {
return name;
}

public void setName(final String name) {
this.name = name;
}

}
Loading