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 all 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
12 changes: 11 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 by default is the thing ID. This property is optional.


### THING_REMOVED

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 All @@ -51,6 +53,7 @@
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
Expand Down Expand Up @@ -84,6 +87,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {

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

private static final String EMPTY_MESSAGE_BODY = "\"\"";

/**
* Constructor.
*
Expand Down Expand Up @@ -122,7 +127,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 Down Expand Up @@ -198,12 +202,14 @@ private static void setTenantSecurityContext(final String tenantId) {
}

/**
* 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.
*
* @param targetID
* the ID of the target/thing
* @param ip
* the ip of the target/thing
* @param message
* the message that contains replyTo property and optionally the name
* in body
* @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, THING_ID_NULL);
Expand All @@ -215,14 +221,29 @@ private void registerTarget(final Message message, final String virtualHost) {

try {
final URI amqpUri = IpUtil.createAmqpUri(virtualHost, replyTo);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
final Target target;
if (isOptionalMessageBodyEmpty(message)) {
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
} else {
checkContentTypeJson(message);

target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri, convertMessage(message, DmfCreateThing.class).getName());

}
LOG.debug("Target {} reported online state.", thingId);
sendUpdateCommandToTarget(target);
} catch (EntityAlreadyExistsException e) {
throw new AmqpRejectAndDontRequeueException("Target already registered, message will be ignored!", e);
} catch (final EntityAlreadyExistsException e) {
throw new AmqpRejectAndDontRequeueException(
"Tried to register previously registered target, message will be ignored!", e);
}
}

private static boolean isOptionalMessageBodyEmpty(final Message message) {
// empty byte array message body is serialized to double-quoted string
// by message converter and should also be considered as empty
return isMessageBodyEmpty(message) || EMPTY_MESSAGE_BODY.equals(new String(message.getBody()));
}

private void sendUpdateCommandToTarget(final Target target) {
if (isMultiAssignmentsEnabled()) {
sendCurrentActionsAsMultiActionToTarget(target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected MessageConverter getMessageConverter() {
return rabbitTemplate.getMessageConverter();
}

private static boolean isMessageBodyEmpty(final Message message) {
protected static boolean isMessageBodyEmpty(final Message message) {
return message.getBody() == null || message.getBody().length == 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
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.DmfDownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
import org.eclipse.hawkbit.repository.ArtifactManagement;
Expand Down Expand Up @@ -70,6 +71,7 @@
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.http.HttpStatus;

Expand All @@ -82,8 +84,8 @@
@Story("AmqpMessage Handler Service Test")
public class AmqpMessageHandlerServiceTest {

private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class.getSimpleName()
+ " was expected, ";
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class
.getSimpleName() + " was expected, ";

private static final String SHA1 = "12345";
private static final String VIRTUAL_HOST = "vHost";
Expand Down Expand Up @@ -136,6 +138,9 @@ public class AmqpMessageHandlerServiceTest {
@Captor
private ArgumentCaptor<String> targetIdCaptor;

@Captor
private ArgumentCaptor<URI> uriCaptor;

@Captor
private ArgumentCaptor<UpdateMode> modeCaptor;

Expand Down Expand Up @@ -176,23 +181,60 @@ public void wrongContentType() {
@Description("Tests the creation of a target/thing by calling the same method that incoming RabbitMQ messages would access.")
public void createThing() {
final String knownThingId = "1";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
final Message message = messageConverter.toMessage(new byte[0], messageProperties);

final Target targetMock = mock(Target.class);

final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
targetIdCaptor = ArgumentCaptor.forClass(String.class);
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, VIRTUAL_HOST);
amqpMessageHandlerService.onMessage(createMessage(new byte[0], getThingCreatedMessageProperties(knownThingId)),
MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);

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

@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 DmfCreateThing targetProperties = new DmfCreateThing();
targetProperties.setName("NonDefaultTargetName");

final Target targetMock = mock(Target.class);

targetIdCaptor = ArgumentCaptor.forClass(String.class);
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(
createMessage(targetProperties, getThingCreatedMessageProperties(knownThingId)),
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 not allowed body in message")
public void createThingWithWrongBody() {
final String knownThingId = "3";

assertThatExceptionOfType(MessageConversionException.class)
.as("MessageConversionException was excepeted due to wrong body")
.isThrownBy(() -> amqpMessageHandlerService.onMessage(
createMessage("Not allowed Body".getBytes(), getThingCreatedMessageProperties(knownThingId)),
MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST));
}

@Test
Expand Down Expand Up @@ -308,15 +350,13 @@ public void invalidEventTopic() {
final Message message = new Message(new byte[0], messageProperties);

assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown message type")
.isThrownBy(() -> amqpMessageHandlerService.onMessage(message, "unknownMessageType", TENANT,
VIRTUAL_HOST));
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown message type").isThrownBy(
() -> amqpMessageHandlerService.onMessage(message, "unknownMessageType", TENANT, VIRTUAL_HOST));

messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown topic")
.isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT,
VIRTUAL_HOST));
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown topic").isThrownBy(() -> amqpMessageHandlerService
.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST));

messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name());
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
Expand Down Expand Up @@ -532,12 +572,12 @@ public void deleteThing() {
final String knownThingId = "1";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
final Message message = messageConverter.toMessage(new byte[0],messageProperties);
final Message message = messageConverter.toMessage(new byte[0], messageProperties);

// test
amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT, VIRTUAL_HOST);

//verify
// verify
verify(controllerManagementMock).deleteExistingTarget(knownThingId);
}

Expand All @@ -547,11 +587,20 @@ public void deleteThingWithoutThingId() {
// prepare invalid message
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
final Message message = messageConverter.toMessage(new byte[0], messageProperties);

assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
.isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT,
VIRTUAL_HOST));
}

private MessageProperties getThingCreatedMessageProperties(String thingId) {
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, thingId);
return messageProperties;
}

private Message createMessage(Object object, MessageProperties messageProperties) {
return messageConverter.toMessage(object, messageProperties);
}
}
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
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