Browse Source

Merge branch 'master' into feature/state-chart

pull/10535/head
Igor Kulikov 3 years ago
parent
commit
77d2552d6d
  1. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 45
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  3. 12
      application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
  4. 17
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  5. 10
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  6. 11
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  8. 16
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/BaseMsgConstructorFactory.java
  9. 42
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  10. 89
      application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
  11. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
  12. 16
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  13. 62
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  14. 23
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  15. 16
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  16. 5
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java
  17. 4
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java
  18. 3
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java
  19. 4
      application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
  20. 3
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java
  21. 3
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java
  22. 3
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java
  23. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  24. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  25. 3
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  26. 20
      common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java
  27. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  28. 287
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java
  29. 100
      common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java
  30. 11
      common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java
  31. 41
      common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java
  32. 16
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  33. 14
      common/util/src/test/java/org/thingsboard/common/util/JacksonUtilTest.java
  34. 9
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  35. 17
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  36. 121
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  37. 10
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  38. 9
      dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java
  39. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  40. 2
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  41. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java
  42. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/event/RuleNodeDebugEventRepository.java
  43. 31
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  44. 5
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  45. 129
      dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java
  46. 34
      dao/src/test/java/org/thingsboard/server/dao/service/RuleChainServiceTest.java
  47. 22
      dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java
  48. 31
      dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java
  49. 3
      pom.xml
  50. 2
      rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java
  51. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java
  52. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java
  53. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java
  54. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java
  55. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java
  56. 27
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java
  57. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java
  58. 30
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
  59. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java
  60. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  61. 120
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java
  62. 60
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java
  63. 16
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java
  64. 296
      transport/lwm2m/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java

5
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -19,6 +19,8 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -116,9 +118,6 @@ import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService; import org.thingsboard.server.service.transport.TbCoreToTransportService;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;

45
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -43,7 +43,6 @@ import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.SortOrder;
@ -74,8 +73,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
@ -254,14 +251,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
} }
private boolean isSendNewRpcAvailable() { private boolean isSendNewRpcAvailable() {
switch (rpcSubmitStrategy) { return switch (rpcSubmitStrategy) {
case SEQUENTIAL_ON_ACK_FROM_DEVICE: case SEQUENTIAL_ON_ACK_FROM_DEVICE -> toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty();
return toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty(); case SEQUENTIAL_ON_RESPONSE_FROM_DEVICE ->
case SEQUENTIAL_ON_RESPONSE_FROM_DEVICE: toDeviceRpcPendingMap.values().stream().filter(ToDeviceRpcRequestMetadata::isDelivered).findAny().isEmpty();
return toDeviceRpcPendingMap.values().stream().filter(ToDeviceRpcRequestMetadata::isDelivered).findAny().isEmpty(); default -> true;
default: };
return true;
}
} }
private void createRpc(ToDeviceRpcRequest request, RpcStatus status) { private void createRpc(ToDeviceRpcRequest request, RpcStatus status) {
@ -927,34 +922,6 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
return systemContext.getEdgeEventService().saveAsync(edgeEvent); return systemContext.getEdgeEventService().saveAsync(edgeEvent);
} }
private KeyValueProto toKeyValueProto(KvEntry kvEntry) {
KeyValueProto.Builder builder = KeyValueProto.newBuilder();
builder.setKey(kvEntry.getKey());
switch (kvEntry.getDataType()) {
case BOOLEAN:
builder.setType(KeyValueType.BOOLEAN_V);
builder.setBoolV(kvEntry.getBooleanValue().get());
break;
case DOUBLE:
builder.setType(KeyValueType.DOUBLE_V);
builder.setDoubleV(kvEntry.getDoubleValue().get());
break;
case LONG:
builder.setType(KeyValueType.LONG_V);
builder.setLongV(kvEntry.getLongValue().get());
break;
case STRING:
builder.setType(KeyValueType.STRING_V);
builder.setStringV(kvEntry.getStrValue().get());
break;
case JSON:
builder.setType(KeyValueType.JSON_V);
builder.setJsonV(kvEntry.getJsonValue().get());
break;
}
return builder.build();
}
void restoreSessions() { void restoreSessions() {
if (systemContext.isLocalCacheType()) { if (systemContext.isLocalCacheType()) {
return; return;

12
application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java

@ -122,15 +122,9 @@ public abstract class AbstractRpcController extends BaseController {
logRpcCall(rpcRequest, rpcError, null); logRpcCall(rpcRequest, rpcError, null);
RpcError error = rpcError.get(); RpcError error = rpcError.get();
switch (error) { switch (error) {
case TIMEOUT: case TIMEOUT -> responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
responseWriter.setResult(new ResponseEntity<>(timeoutStatus)); case NO_ACTIVE_CONNECTION -> responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus));
break; default -> responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
case NO_ACTIVE_CONNECTION:
responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus));
break;
default:
responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
break;
} }
} else { } else {
Optional<String> responseData = response.getResponse(); Optional<String> responseData = response.getResponse();

17
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -47,8 +47,8 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
@ -80,6 +80,7 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -340,18 +341,8 @@ public class RuleChainController extends BaseController {
RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId));
checkRuleNode(ruleNodeId, Operation.READ); checkRuleNode(ruleNodeId, Operation.READ);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
List<EventInfo> events = eventService.findLatestEvents(tenantId, ruleNodeId, EventType.DEBUG_RULE_NODE, 2); return Optional.ofNullable(eventService.findLatestDebugRuleNodeInEvent(tenantId, ruleNodeId))
JsonNode result = null; .map(EventInfo::getBody).orElse(null);
if (events != null) {
for (EventInfo event : events) {
JsonNode body = event.getBody();
if (body.has("type") && body.get("type").asText().equals("IN")) {
result = body;
break;
}
}
}
return result;
} }
@ApiOperation(value = "Is TBEL script executor enabled", @ApiOperation(value = "Is TBEL script executor enabled",

10
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -25,7 +25,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -36,7 +35,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -49,9 +47,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.kv.AggregationParams;
import org.thingsboard.server.common.data.kv.IntervalType;
import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -65,6 +60,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AggregationParams;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery; import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
@ -74,6 +70,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.IntervalType;
import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
@ -379,7 +376,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV1( public DeferredResult<ResponseEntity> saveEntityAttributesV1(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope") AttributeScope scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request); return saveAttributes(getTenantId(), entityId, scope, request);
@ -918,4 +915,5 @@ public class TelemetryController extends BaseController {
} }
return entry.getValue(); return entry.getValue();
} }
} }

11
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
@ -127,20 +128,20 @@ public class EntityActionService {
} else { } else {
entityNode = JacksonUtil.newObjectNode(); entityNode = JacksonUtil.newObjectNode();
if (actionType == ActionType.ATTRIBUTES_UPDATED) { if (actionType == ActionType.ATTRIBUTES_UPDATED) {
String scope = extractParameter(String.class, 0, additionalInfo); AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
metaData.putValue(DataConstants.SCOPE, scope); metaData.putValue(DataConstants.SCOPE, scope.name());
if (attributes != null) { if (attributes != null) {
for (AttributeKvEntry attr : attributes) { for (AttributeKvEntry attr : attributes) {
JacksonUtil.addKvEntry(entityNode, attr); JacksonUtil.addKvEntry(entityNode, attr);
} }
} }
} else if (actionType == ActionType.ATTRIBUTES_DELETED) { } else if (actionType == ActionType.ATTRIBUTES_DELETED) {
String scope = extractParameter(String.class, 0, additionalInfo); AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<String> keys = extractParameter(List.class, 1, additionalInfo); List<String> keys = extractParameter(List.class, 1, additionalInfo);
metaData.putValue(DataConstants.SCOPE, scope); metaData.putValue(DataConstants.SCOPE, scope.name());
ArrayNode attrsArrayNode = entityNode.putArray("attributes"); ArrayNode attrsArrayNode = entityNode.putArray("attributes");
if (keys != null) { if (keys != null) {
keys.forEach(attrsArrayNode::add); keys.forEach(attrsArrayNode::add);
@ -249,7 +250,7 @@ public class EntityActionService {
return result; return result;
} }
private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception { private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) {
if (timeseries != null && !timeseries.isEmpty()) { if (timeseries != null && !timeseries.isEmpty()) {
ArrayNode result = entityNode.putArray("timeseries"); ArrayNode result = entityNode.putArray("timeseries");
Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream() Map<Long, List<TsKvEntry>> groupedTelemetry = timeseries.stream()

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -557,8 +557,7 @@ public final class EdgeGrpcSession implements Closeable {
} }
case ATTRIBUTES_UPDATED, POST_ATTRIBUTES, ATTRIBUTES_DELETED, TIMESERIES_UPDATED -> case ATTRIBUTES_UPDATED, POST_ATTRIBUTES, ATTRIBUTES_DELETED, TIMESERIES_UPDATED ->
downlinkMsg = ctx.getTelemetryProcessor().convertTelemetryEventToDownlink(edge, edgeEvent); downlinkMsg = ctx.getTelemetryProcessor().convertTelemetryEventToDownlink(edge, edgeEvent);
default -> default -> log.warn("[{}][{}] Unsupported action type [{}]", this.tenantId, this.sessionId, edgeEvent.getAction());
log.warn("[{}][{}] Unsupported action type [{}]", this.tenantId, this.sessionId, edgeEvent.getAction());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[{}][{}] Exception during converting edge event to downlink msg", this.tenantId, this.sessionId, e); log.error("[{}][{}] Exception during converting edge event to downlink msg", this.tenantId, this.sessionId, e);

16
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/BaseMsgConstructorFactory.java

@ -31,16 +31,10 @@ public abstract class BaseMsgConstructorFactory<T extends MsgConstructor, U exte
protected U v2Constructor; protected U v2Constructor;
public MsgConstructor getMsgConstructorByEdgeVersion(EdgeVersion edgeVersion) { public MsgConstructor getMsgConstructorByEdgeVersion(EdgeVersion edgeVersion) {
switch (edgeVersion) { return switch (edgeVersion) {
case V_3_3_0: case V_3_3_0, V_3_3_3, V_3_4_0, V_3_6_0, V_3_6_1 -> v1Constructor;
case V_3_3_3: default -> v2Constructor;
case V_3_4_0: };
case V_3_6_0:
case V_3_6_1:
return v1Constructor;
case V_3_6_2:
default:
return v2Constructor;
}
} }
} }

42
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java

@ -23,10 +23,13 @@ import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -53,7 +56,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
@ -66,8 +68,6 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -317,36 +317,22 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonNode body) { JsonNode body) {
EntityId entityId; EntityId entityId;
switch (entityType) { switch (entityType) {
case DEVICE: case DEVICE -> entityId = new DeviceId(entityUUID);
entityId = new DeviceId(entityUUID); case ASSET -> entityId = new AssetId(entityUUID);
break; case ENTITY_VIEW -> entityId = new EntityViewId(entityUUID);
case ASSET: case DASHBOARD -> entityId = new DashboardId(entityUUID);
entityId = new AssetId(entityUUID); case TENANT -> entityId = TenantId.fromUUID(entityUUID);
break; case CUSTOMER -> entityId = new CustomerId(entityUUID);
case ENTITY_VIEW: case USER -> entityId = new UserId(entityUUID);
entityId = new EntityViewId(entityUUID); case EDGE -> entityId = new EdgeId(entityUUID);
break; default -> {
case DASHBOARD:
entityId = new DashboardId(entityUUID);
break;
case TENANT:
entityId = TenantId.fromUUID(entityUUID);
break;
case CUSTOMER:
entityId = new CustomerId(entityUUID);
break;
case USER:
entityId = new UserId(entityUUID);
break;
case EDGE:
entityId = new EdgeId(entityUUID);
break;
default:
log.warn("[{}] Unsupported edge event type [{}]", tenantId, entityType); log.warn("[{}] Unsupported edge event type [{}]", tenantId, entityType);
return null; return null;
}
} }
String bodyJackson = JacksonUtil.toString(body); String bodyJackson = JacksonUtil.toString(body);
return bodyJackson == null ? null : return bodyJackson == null ? null :
entityDataMsgConstructor.constructEntityDataMsg(tenantId, entityId, actionType, JsonParser.parseString(bodyJackson)); entityDataMsgConstructor.constructEntityDataMsg(tenantId, entityId, actionType, JsonParser.parseString(bodyJackson));
} }
} }

89
application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java

@ -69,80 +69,71 @@ public class EntityStateSourcingListener {
@TransactionalEventListener(fallbackExecution = true) @TransactionalEventListener(fallbackExecution = true)
public void handleEvent(SaveEntityEvent<?> event) { public void handleEvent(SaveEntityEvent<?> event) {
log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event);
TenantId tenantId = event.getTenantId(); TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId(); EntityId entityId = event.getEntityId();
EntityType entityType = entityId.getEntityType(); EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity save event: {}", tenantId, entityType, entityId, event);
boolean isCreated = event.getCreated() != null && event.getCreated(); boolean isCreated = event.getCreated() != null && event.getCreated();
ComponentLifecycleEvent lifecycleEvent = isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED; ComponentLifecycleEvent lifecycleEvent = isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED;
switch (entityType) { switch (entityType) {
case ASSET: case ASSET, ASSET_PROFILE, ENTITY_VIEW, NOTIFICATION_RULE -> {
case ASSET_PROFILE:
case ENTITY_VIEW:
case NOTIFICATION_RULE:
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent);
break; }
case RULE_CHAIN: case RULE_CHAIN -> {
RuleChain ruleChain = (RuleChain) event.getEntity(); RuleChain ruleChain = (RuleChain) event.getEntity();
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), lifecycleEvent); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), lifecycleEvent);
} }
break; }
case TENANT: case TENANT -> {
Tenant tenant = (Tenant) event.getEntity(); Tenant tenant = (Tenant) event.getEntity();
onTenantUpdate(tenant, lifecycleEvent); onTenantUpdate(tenant, lifecycleEvent);
break; }
case TENANT_PROFILE: case TENANT_PROFILE -> {
TenantProfile tenantProfile = (TenantProfile) event.getEntity(); TenantProfile tenantProfile = (TenantProfile) event.getEntity();
onTenantProfileUpdate(tenantProfile, lifecycleEvent); onTenantProfileUpdate(tenantProfile, lifecycleEvent);
break; }
case DEVICE: case DEVICE -> {
onDeviceUpdate(event.getEntity(), event.getOldEntity()); onDeviceUpdate(event.getEntity(), event.getOldEntity());
break; }
case DEVICE_PROFILE: case DEVICE_PROFILE -> {
DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); DeviceProfile deviceProfile = (DeviceProfile) event.getEntity();
onDeviceProfileUpdate(deviceProfile, event.getOldEntity(), isCreated); onDeviceProfileUpdate(deviceProfile, event.getOldEntity(), isCreated);
break; }
case EDGE: case EDGE -> {
handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent); handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent);
break; }
case TB_RESOURCE: case TB_RESOURCE -> {
TbResource tbResource = (TbResource) event.getEntity(); TbResource tbResource = (TbResource) event.getEntity();
tbClusterService.onResourceChange(tbResource, null); tbClusterService.onResourceChange(tbResource, null);
break; }
case API_USAGE_STATE: case API_USAGE_STATE -> {
ApiUsageState apiUsageState = (ApiUsageState) event.getEntity(); ApiUsageState apiUsageState = (ApiUsageState) event.getEntity();
tbClusterService.onApiStateChange(apiUsageState, null); tbClusterService.onApiStateChange(apiUsageState, null);
break; }
default: default -> {}
break;
} }
} }
@TransactionalEventListener(fallbackExecution = true) @TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent<?> event) { public void handleEvent(DeleteEntityEvent<?> event) {
log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event);
TenantId tenantId = event.getTenantId(); TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId(); EntityId entityId = event.getEntityId();
EntityType entityType = entityId.getEntityType(); EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity deletion event: {}", tenantId, entityType, entityId, event);
switch (entityType) { switch (entityType) {
case ASSET: case ASSET, ASSET_PROFILE, ENTITY_VIEW, CUSTOMER, EDGE, NOTIFICATION_RULE -> {
case ASSET_PROFILE:
case ENTITY_VIEW:
case CUSTOMER:
case EDGE:
case NOTIFICATION_RULE:
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED);
break; }
case NOTIFICATION_REQUEST: case NOTIFICATION_REQUEST -> {
NotificationRequest request = (NotificationRequest) event.getEntity(); NotificationRequest request = (NotificationRequest) event.getEntity();
if (request.isScheduled()) { if (request.isScheduled()) {
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED);
} }
break; }
case RULE_CHAIN: case RULE_CHAIN -> {
RuleChain ruleChain = (RuleChain) event.getEntity(); RuleChain ruleChain = (RuleChain) event.getEntity();
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
Set<RuleChainId> referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {}); Set<RuleChainId> referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {});
@ -152,29 +143,28 @@ public class EntityStateSourcingListener {
} }
tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED);
} }
break; }
case TENANT: case TENANT -> {
Tenant tenant = (Tenant) event.getEntity(); Tenant tenant = (Tenant) event.getEntity();
onTenantDeleted(tenant); onTenantDeleted(tenant);
break; }
case TENANT_PROFILE: case TENANT_PROFILE -> {
TenantProfile tenantProfile = (TenantProfile) event.getEntity(); TenantProfile tenantProfile = (TenantProfile) event.getEntity();
tbClusterService.onTenantProfileDelete(tenantProfile, null); tbClusterService.onTenantProfileDelete(tenantProfile, null);
break; }
case DEVICE: case DEVICE -> {
Device device = (Device) event.getEntity(); Device device = (Device) event.getEntity();
tbClusterService.onDeviceDeleted(tenantId, device, null); tbClusterService.onDeviceDeleted(tenantId, device, null);
break; }
case DEVICE_PROFILE: case DEVICE_PROFILE -> {
DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); DeviceProfile deviceProfile = (DeviceProfile) event.getEntity();
onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), deviceProfile); onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), deviceProfile);
break; }
case TB_RESOURCE: case TB_RESOURCE -> {
TbResourceInfo tbResource = (TbResourceInfo) event.getEntity(); TbResourceInfo tbResource = (TbResourceInfo) event.getEntity();
tbClusterService.onResourceDeleted(tbResource, null); tbClusterService.onResourceDeleted(tbResource, null);
break; }
default: default -> {}
break;
} }
} }
@ -186,8 +176,7 @@ public class EntityStateSourcingListener {
&& event.getEntity() instanceof DeviceCredentials) { && event.getEntity() instanceof DeviceCredentials) {
tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(event.getTenantId(), tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(event.getTenantId(),
(DeviceId) event.getEntityId(), (DeviceCredentials) event.getEntity()), null); (DeviceId) event.getEntityId(), (DeviceCredentials) event.getEntity()), null);
} else if (ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType()) && event.getEntity() instanceof Device) { } else if (ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType()) && event.getEntity() instanceof Device device) {
Device device = (Device) event.getEntity();
Tenant tenant = JacksonUtil.fromString(event.getBody(), Tenant.class); Tenant tenant = JacksonUtil.fromString(event.getBody(), Tenant.class);
if (tenant != null) { if (tenant != null) {
tbClusterService.onDeviceAssignedToTenant(tenant.getId(), device); tbClusterService.onDeviceAssignedToTenant(tenant.getId(), device);

18
application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java

@ -16,12 +16,10 @@
package org.thingsboard.server.service.entitiy.tenant; package org.thingsboard.server.service.entitiy.tenant;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -45,25 +43,19 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
private final TbQueueService tbQueueService; private final TbQueueService tbQueueService;
private final TenantProfileService tenantProfileService; private final TenantProfileService tenantProfileService;
private final EntitiesVersionControlService versionControlService; private final EntitiesVersionControlService versionControlService;
private final ApplicationEventPublisher eventPublisher;
@Override @Override
public Tenant save(Tenant tenant) throws Exception { public Tenant save(Tenant tenant) throws Exception {
boolean created = tenant.getId() == null; boolean created = tenant.getId() == null;
Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null;
Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant, !created)); Tenant savedTenant = tenantService.saveTenant(tenant, tenantId -> {
if (created) { installScripts.createDefaultRuleChains(tenantId);
installScripts.createDefaultRuleChains(savedTenant.getId()); installScripts.createDefaultEdgeRuleChains(tenantId);
installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); installScripts.createDefaultTenantDashboards(tenantId, null);
installScripts.createDefaultTenantDashboards(savedTenant.getId(), null); });
}
tenantProfileCache.evict(savedTenant.getId()); tenantProfileCache.evict(savedTenant.getId());
if (created) {
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entityId(savedTenant.getId()).entity(savedTenant).created(true).build());
}
TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null;
TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId());
tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile);

16
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -151,17 +151,18 @@ public class InstallScripts {
} }
} }
public void createDefaultRuleChains(TenantId tenantId) throws IOException { public void createDefaultRuleChains(TenantId tenantId) {
Path tenantChainsDir = getTenantRuleChainsDir(); Path tenantChainsDir = getTenantRuleChainsDir();
loadRuleChainsFromPath(tenantId, tenantChainsDir); loadRuleChainsFromPath(tenantId, tenantChainsDir);
} }
public void createDefaultEdgeRuleChains(TenantId tenantId) throws IOException { public void createDefaultEdgeRuleChains(TenantId tenantId) {
Path edgeChainsDir = getEdgeRuleChainsDir(); Path edgeChainsDir = getEdgeRuleChainsDir();
loadRuleChainsFromPath(tenantId, edgeChainsDir); loadRuleChainsFromPath(tenantId, edgeChainsDir);
} }
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException { @SneakyThrows
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) {
findRuleChainsFromPath(ruleChainsPath).forEach(path -> { findRuleChainsFromPath(ruleChainsPath).forEach(path -> {
try { try {
createRuleChainFromFile(tenantId, path, null); createRuleChainFromFile(tenantId, path, null);
@ -329,17 +330,18 @@ public class InstallScripts {
} }
} }
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { public void loadDashboards(TenantId tenantId, CustomerId customerId) {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir); loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
} }
public void createDefaultTenantDashboards(TenantId tenantId, CustomerId customerId) throws Exception { public void createDefaultTenantDashboards(TenantId tenantId, CustomerId customerId) {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DASHBOARDS_DIR); Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir); loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
} }
private void loadDashboardsFromDir(TenantId tenantId, CustomerId customerId, Path dashboardsDir) throws IOException { @SneakyThrows
private void loadDashboardsFromDir(TenantId tenantId, CustomerId customerId, Path dashboardsDir) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach( dirStream.forEach(
path -> { path -> {
@ -414,7 +416,7 @@ public class InstallScripts {
} }
); );
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to load resources lwm2m object model from file: [{}]", resourceLwm2mPath.toString()); log.error("Unable to load resources lwm2m object model from file: [{}]", resourceLwm2mPath);
throw new RuntimeException("resource lwm2m object model from file", e); throw new RuntimeException("resource lwm2m object model from file", e);
} }
} }

62
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -208,52 +208,22 @@ public class AccessValidator {
public void validate(SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { public void validate(SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
switch (entityId.getEntityType()) { switch (entityId.getEntityType()) {
case DEVICE: case DEVICE -> validateDevice(currentUser, operation, entityId, callback);
validateDevice(currentUser, operation, entityId, callback); case DEVICE_PROFILE -> validateDeviceProfile(currentUser, operation, entityId, callback);
return; case ASSET -> validateAsset(currentUser, operation, entityId, callback);
case DEVICE_PROFILE: case ASSET_PROFILE -> validateAssetProfile(currentUser, operation, entityId, callback);
validateDeviceProfile(currentUser, operation, entityId, callback); case RULE_CHAIN -> validateRuleChain(currentUser, operation, entityId, callback);
return; case CUSTOMER -> validateCustomer(currentUser, operation, entityId, callback);
case ASSET: case TENANT -> validateTenant(currentUser, operation, entityId, callback);
validateAsset(currentUser, operation, entityId, callback); case TENANT_PROFILE -> validateTenantProfile(currentUser, operation, entityId, callback);
return; case USER -> validateUser(currentUser, operation, entityId, callback);
case ASSET_PROFILE: case ENTITY_VIEW -> validateEntityView(currentUser, operation, entityId, callback);
validateAssetProfile(currentUser, operation, entityId, callback); case EDGE -> validateEdge(currentUser, operation, entityId, callback);
return; case API_USAGE_STATE -> validateApiUsageState(currentUser, operation, entityId, callback);
case RULE_CHAIN: case TB_RESOURCE -> validateResource(currentUser, operation, entityId, callback);
validateRuleChain(currentUser, operation, entityId, callback); case OTA_PACKAGE -> validateOtaPackage(currentUser, operation, entityId, callback);
return; case RPC -> validateRpc(currentUser, operation, entityId, callback);
case CUSTOMER: default ->
validateCustomer(currentUser, operation, entityId, callback);
return;
case TENANT:
validateTenant(currentUser, operation, entityId, callback);
return;
case TENANT_PROFILE:
validateTenantProfile(currentUser, operation, entityId, callback);
return;
case USER:
validateUser(currentUser, operation, entityId, callback);
return;
case ENTITY_VIEW:
validateEntityView(currentUser, operation, entityId, callback);
return;
case EDGE:
validateEdge(currentUser, operation, entityId, callback);
return;
case API_USAGE_STATE:
validateApiUsageState(currentUser, operation, entityId, callback);
return;
case TB_RESOURCE:
validateResource(currentUser, operation, entityId, callback);
return;
case OTA_PACKAGE:
validateOtaPackage(currentUser, operation, entityId, callback);
return;
case RPC:
validateRpc(currentUser, operation, entityId, callback);
return;
default:
//TODO: add support of other entities //TODO: add support of other entities
throw new IllegalStateException("Not Implemented!"); throw new IllegalStateException("Not Implemented!");
} }

23
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -236,14 +236,14 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
@Override @Override
public void onSuccess(Void unused) { public void onSuccess(Void unused) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
null, ActionType.ATTRIBUTES_UPDATED, null, scope, attributes); null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes);
} }
@Override @Override
public void onFailure(Throwable throwable) { public void onFailure(Throwable throwable) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable), null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
scope, attributes); AttributeScope.valueOf(scope), attributes);
throw new RuntimeException(throwable); throw new RuntimeException(throwable);
} }
@ -301,18 +301,13 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
private final DataType dataType; private final DataType dataType;
public JsonPrimitive toJsonPrimitive() { public JsonPrimitive toJsonPrimitive() {
switch (dataType) { return switch (dataType) {
case STRING: case STRING -> new JsonPrimitive((String) value);
return new JsonPrimitive((String) value); case LONG -> new JsonPrimitive((Long) value);
case LONG: case DOUBLE -> new JsonPrimitive((Double) value);
return new JsonPrimitive((Long) value); case BOOLEAN -> new JsonPrimitive((Boolean) value);
case DOUBLE: default -> null;
return new JsonPrimitive((Double) value); };
case BOOLEAN:
return new JsonPrimitive((Boolean) value);
default:
return null;
}
} }
public String stringValue() { public String stringValue() {

16
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -663,7 +663,7 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId()); savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class); savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId; TenantId tenantId = differentTenantId;
await().atMost(10, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.until(() -> { .until(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId);
return !tpi.getTenantId().get().isSysTenantId(); return !tpi.getTenantId().get().isSysTenantId();
@ -677,7 +677,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile.setIsolatedTbRuleEngine(false); tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList()); tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList());
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
await().atMost(10, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId) .until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId)
.getTenantId().get().isSysTenantId()); .getTenantId().get().isSysTenantId());
@ -689,11 +689,11 @@ public class TenantControllerTest extends AbstractControllerTest {
submittedMsgs.add(tbMsg.getId()); submittedMsgs.add(tbMsg.getId());
Thread.sleep(timeLeft / msgs); Thread.sleep(timeLeft / msgs);
} }
await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic)); verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic));
}); });
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
for (UUID msgId : submittedMsgs) { for (UUID msgId : submittedMsgs) {
verify(actorContext).tell(argThat(msg -> { verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(msgId); return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(msgId);
@ -718,13 +718,13 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId()); savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class); savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId; TenantId tenantId = differentTenantId;
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull(); assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull();
}); });
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId);
assertThat(tpi.getTenantId()).hasValue(tenantId); assertThat(tpi.getTenantId()).hasValue(tenantId);
TbMsg tbMsg = publishTbMsg(tenantId, tpi); TbMsg tbMsg = publishTbMsg(tenantId, tpi);
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verify(actorContext).tell(argThat(msg -> { verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId()); return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId());
})); }));
@ -732,7 +732,7 @@ public class TenantControllerTest extends AbstractControllerTest {
deleteDifferentTenant(); deleteDifferentTenant();
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNull(); assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNull();
assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId)) assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId))
.isInstanceOf(TenantNotFoundException.class); .isInstanceOf(TenantNotFoundException.class);
@ -752,7 +752,7 @@ public class TenantControllerTest extends AbstractControllerTest {
} }
private void verifyUsedQueueAndMessage(String queue, TenantId tenantId, EntityId entityId, String msgType, Runnable action, Consumer<TopicPartitionInfo> tpiAssert) { private void verifyUsedQueueAndMessage(String queue, TenantId tenantId, EntityId entityId, String msgType, Runnable action, Consumer<TopicPartitionInfo> tpiAssert) {
await().atMost(15, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId);
tpiAssert.accept(tpi); tpiAssert.accept(tpi);

5
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java

@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.Resource;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.coapserver.DefaultCoapServerService; import org.thingsboard.server.coapserver.DefaultCoapServerService;
@ -60,15 +59,11 @@ public class CoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributes
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processJsonTestSubscribeToAttributesUpdates(false); processJsonTestSubscribeToAttributesUpdates(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
processJsonTestSubscribeToAttributesUpdates(true); processJsonTestSubscribeToAttributesUpdates(true);

4
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.attributes.updates;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -45,12 +44,11 @@ public class CoapAttributesUpdatesJsonIntegrationTest extends AbstractCoapAttrib
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processJsonTestSubscribeToAttributesUpdates(false); processJsonTestSubscribeToAttributesUpdates(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
processJsonTestSubscribeToAttributesUpdates(true); processJsonTestSubscribeToAttributesUpdates(true);

3
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.attributes.updates;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -44,12 +43,10 @@ public class CoapAttributesUpdatesProtoIntegrationTest extends AbstractCoapAttri
public void afterTest() throws Exception { public void afterTest() throws Exception {
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processProtoTestSubscribeToAttributesUpdates(false); processProtoTestSubscribeToAttributesUpdates(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
processProtoTestSubscribeToAttributesUpdates(true); processProtoTestSubscribeToAttributesUpdates(true);

4
application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java

@ -26,7 +26,6 @@ import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -69,7 +68,6 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
private static final List<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5"); private static final List<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
@ -83,7 +81,6 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testConfirmableRequests() throws Exception { public void testConfirmableRequests() throws Exception {
boolean confirmable = true; boolean confirmable = true;
@ -92,7 +89,6 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
processTestRequestAttributesValuesFromTheServer(confirmable); processTestRequestAttributesValuesFromTheServer(confirmable);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testNonConfirmableRequests() throws Exception { public void testNonConfirmableRequests() throws Exception {
boolean confirmable = false; boolean confirmable = false;

3
application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java

@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
@ -83,13 +82,11 @@ public class CoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerS
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapOneWayRpc() throws Exception { public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest(false); processOneWayRpcTest(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapTwoWayRpc() throws Exception { public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false);

3
application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -44,13 +43,11 @@ public class CoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSide
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapOneWayRpc() throws Exception { public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest(false); processOneWayRpcTest(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapTwoWayRpc() throws Exception { public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false);

3
application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -45,13 +44,11 @@ public class CoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSid
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapOneWayRpc() throws Exception { public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest(true); processOneWayRpcTest(true);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapTwoWayRpc() throws Exception { public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", true); processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", true);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -35,6 +35,8 @@ public interface EventService {
List<EventInfo> findLatestEvents(TenantId tenantId, EntityId entityId, EventType eventType, int limit); List<EventInfo> findLatestEvents(TenantId tenantId, EntityId entityId, EventType eventType, int limit);
EventInfo findLatestDebugRuleNodeInEvent(TenantId tenantId, EntityId entityId);
PageData<EventInfo> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink); PageData<EventInfo> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
void removeEvents(TenantId tenantId, EntityId entityId); void removeEvents(TenantId tenantId, EntityId entityId);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
public interface TenantService extends EntityDaoService { public interface TenantService extends EntityDaoService {
@ -36,7 +37,7 @@ public interface TenantService extends EntityDaoService {
Tenant saveTenant(Tenant tenant); Tenant saveTenant(Tenant tenant);
Tenant saveTenant(Tenant tenant, boolean publishSaveEvent); Tenant saveTenant(Tenant tenant, Consumer<TenantId> defaultEntitiesCreator);
boolean tenantExists(TenantId tenantId); boolean tenantExists(TenantId tenantId);

3
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setConnectRequestMsg(ConnectRequestMsg.newBuilder()
.setEdgeRoutingKey(edgeKey) .setEdgeRoutingKey(edgeKey)
.setEdgeSecret(edgeSecret) .setEdgeSecret(edgeSecret)
.setEdgeVersion(EdgeVersion.V_3_6_2) .setEdgeVersion(EdgeVersion.V_3_7_0)
.setMaxInboundMessageSize(maxInboundMessageSize) .setMaxInboundMessageSize(maxInboundMessageSize)
.build()) .build())
.build()); .build());
@ -248,4 +248,5 @@ public class EdgeGrpcClient implements EdgeRpcClient {
uplinkMsgLock.unlock(); uplinkMsgLock.unlock();
} }
} }
} }

20
common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java

@ -258,31 +258,31 @@ public class ProtoUtils {
.setLastUpdateTs(attributeKvEntry.getLastUpdateTs()) .setLastUpdateTs(attributeKvEntry.getLastUpdateTs())
.setKey(attributeKvEntry.getKey()); .setKey(attributeKvEntry.getKey());
switch (attributeKvEntry.getDataType()) { switch (attributeKvEntry.getDataType()) {
case BOOLEAN: case BOOLEAN -> {
attributeKvEntry.getBooleanValue().ifPresent(attributeValueBuilder::setBoolV); attributeKvEntry.getBooleanValue().ifPresent(attributeValueBuilder::setBoolV);
attributeValueBuilder.setHasV(attributeKvEntry.getBooleanValue().isPresent()); attributeValueBuilder.setHasV(attributeKvEntry.getBooleanValue().isPresent());
attributeValueBuilder.setType(TransportProtos.KeyValueType.BOOLEAN_V); attributeValueBuilder.setType(TransportProtos.KeyValueType.BOOLEAN_V);
break; }
case STRING: case STRING -> {
attributeKvEntry.getStrValue().ifPresent(attributeValueBuilder::setStringV); attributeKvEntry.getStrValue().ifPresent(attributeValueBuilder::setStringV);
attributeValueBuilder.setHasV(attributeKvEntry.getStrValue().isPresent()); attributeValueBuilder.setHasV(attributeKvEntry.getStrValue().isPresent());
attributeValueBuilder.setType(TransportProtos.KeyValueType.STRING_V); attributeValueBuilder.setType(TransportProtos.KeyValueType.STRING_V);
break; }
case DOUBLE: case DOUBLE -> {
attributeKvEntry.getDoubleValue().ifPresent(attributeValueBuilder::setDoubleV); attributeKvEntry.getDoubleValue().ifPresent(attributeValueBuilder::setDoubleV);
attributeValueBuilder.setHasV(attributeKvEntry.getDoubleValue().isPresent()); attributeValueBuilder.setHasV(attributeKvEntry.getDoubleValue().isPresent());
attributeValueBuilder.setType(TransportProtos.KeyValueType.DOUBLE_V); attributeValueBuilder.setType(TransportProtos.KeyValueType.DOUBLE_V);
break; }
case LONG: case LONG -> {
attributeKvEntry.getLongValue().ifPresent(attributeValueBuilder::setLongV); attributeKvEntry.getLongValue().ifPresent(attributeValueBuilder::setLongV);
attributeValueBuilder.setHasV(attributeKvEntry.getLongValue().isPresent()); attributeValueBuilder.setHasV(attributeKvEntry.getLongValue().isPresent());
attributeValueBuilder.setType(TransportProtos.KeyValueType.LONG_V); attributeValueBuilder.setType(TransportProtos.KeyValueType.LONG_V);
break; }
case JSON: case JSON -> {
attributeKvEntry.getJsonValue().ifPresent(attributeValueBuilder::setJsonV); attributeKvEntry.getJsonValue().ifPresent(attributeValueBuilder::setJsonV);
attributeValueBuilder.setHasV(attributeKvEntry.getJsonValue().isPresent()); attributeValueBuilder.setHasV(attributeKvEntry.getJsonValue().isPresent());
attributeValueBuilder.setType(TransportProtos.KeyValueType.JSON_V); attributeValueBuilder.setType(TransportProtos.KeyValueType.JSON_V);
break; }
} }
builder.addValues(attributeValueBuilder.build()); builder.addValues(attributeValueBuilder.build());
} }

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -229,7 +229,7 @@ public class LwM2mClient {
this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode); this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode);
return true; return true;
} else { } else {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = getLwM2mPathFromString(pathRezIdVer);
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) { if (resourceModel != null) {
this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel)); this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel));
@ -257,7 +257,7 @@ public class LwM2mClient {
} }
public String getRezIdByResourceNameAndObjectInstanceId(String resourceName, String pathObjectInstanceIdVer, LwM2mModelProvider modelProvider) { public String getRezIdByResourceNameAndObjectInstanceId(String resourceName, String pathObjectInstanceIdVer, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathObjectInstanceIdVer)); LwM2mPath pathIds = getLwM2mPathFromString(pathObjectInstanceIdVer);
if (pathIds.isObjectInstance()) { if (pathIds.isObjectInstance()) {
Set<Integer> rezIds = modelProvider.getObjectModel(registration) Set<Integer> rezIds = modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()).resources.entrySet() .getObjectModel(pathIds.getObjectId()).resources.entrySet()
@ -271,7 +271,7 @@ public class LwM2mClient {
} }
public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) { public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); LwM2mPath pathIds = getLwM2mPathFromString(pathIdVer);
String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId())); String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId()));
String verRez = getVerFromPathIdVerOrId(pathIdVer); String verRez = getVerFromPathIdVerOrId(pathIdVer);
return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration)
@ -289,7 +289,7 @@ public class LwM2mClient {
public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) { public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) {
try { try {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); LwM2mPath pathIds = getLwM2mPathFromString(pathIdVer);
String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId())); String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId()));
String verRez = getVerFromPathIdVerOrId(pathIdVer); String verRez = getVerFromPathIdVerOrId(pathIdVer);
return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration)
@ -309,7 +309,7 @@ public class LwM2mClient {
public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverter converter) { LwM2mValueConverter converter) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = getLwM2mPathFromString(pathRezIdVer);
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet(); Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration) Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()).resources; .getObjectModel(pathIds.getObjectId()).resources;
@ -329,7 +329,7 @@ public class LwM2mClient {
*/ */
public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverter converter) { LwM2mValueConverter converter) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = getLwM2mPathFromString(pathRezIdVer);
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet(); Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration) Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()).resources; .getObjectModel(pathIds.getObjectId()).resources;
@ -370,7 +370,7 @@ public class LwM2mClient {
} }
public String isValidObjectVersion(String path) { public String isValidObjectVersion(String path) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); LwM2mPath pathIds = getLwM2mPathFromString(path);
LwM2m.Version verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); LwM2m.Version verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
if (verSupportedObject == null) { if (verSupportedObject == null) {
return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId()); return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId());
@ -390,7 +390,7 @@ public class LwM2mClient {
public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) { public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) {
Set<String> key = getKeysEqualsIdVer(pathIdVer); Set<String> key = getKeysEqualsIdVer(pathIdVer);
key.forEach(pathRez -> { key.forEach(pathRez -> {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRez)); LwM2mPath pathIds = getLwM2mPathFromString(pathRez);
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) { if (resourceModel != null) {
this.resources.get(pathRez).setResourceModel(resourceModel); this.resources.get(pathRez).setResourceModel(resourceModel);
@ -410,7 +410,7 @@ public class LwM2mClient {
} }
private void saveResourceModel(String pathRez, LwM2mModelProvider modelProvider) { private void saveResourceModel(String pathRez, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRez)); LwM2mPath pathIds = getLwM2mPathFromString(pathRez);
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
this.resources.get(pathRez).setResourceModel(resourceModel); this.resources.get(pathRez).setResourceModel(resourceModel);
} }
@ -456,5 +456,9 @@ public class LwM2mClient {
return result; return result;
} }
public LwM2mPath getLwM2mPathFromString(String path) {
return new LwM2mPath(fromVersionedIdToObjectId(path));
}
} }

287
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java

@ -15,8 +15,12 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.store.util; package org.thingsboard.server.transport.lwm2m.server.store.util;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
@ -25,23 +29,35 @@ import org.eclipse.leshan.core.node.LwM2mNodeException;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.util.datatype.ULong;
import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.time.Instant;
import java.util.Base64; import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import static org.thingsboard.common.util.JacksonUtil.toJsonNode;
public class LwM2MClientSerDes { public class LwM2MClientSerDes {
public static final String VALUE = "value"; public static final String VALUE = "value";
private static final RegistrationSerDes registrationSerDes = new RegistrationSerDes();
@SneakyThrows @SneakyThrows
public static byte[] serialize(LwM2mClient client) { public static byte[] serialize(LwM2mClient client) {
JsonObject o = new JsonObject(); JsonObject o = new JsonObject();
o.addProperty("nodeId", "client.getNodeId()"); o.addProperty("nodeId", client.getNodeId());
o.addProperty("endpoint", client.getEndpoint()); o.addProperty("endpoint", client.getEndpoint());
JsonObject resources = new JsonObject(); JsonObject resources = new JsonObject();
@ -92,8 +108,15 @@ public class LwM2MClientSerDes {
o.addProperty("pagingTransmissionWindow", client.getPagingTransmissionWindow()); o.addProperty("pagingTransmissionWindow", client.getPagingTransmissionWindow());
} }
if (client.getRegistration() != null) { if (client.getRegistration() != null) {
RegistrationSerDes regDez = new RegistrationSerDes(); String registrationAddress = client.getRegistration().getAddress().toString();
o.addProperty("registration", regDez.jSerialize(client.getRegistration()).toString()); JsonNode registrationNode = registrationSerDes.jSerialize(client.getRegistration());
if (!registrationAddress.equals(registrationNode.get("transportdata").get("address").asText())){
ObjectNode actualRegAddress = (ObjectNode)registrationNode.get("transportdata");
actualRegAddress.put("address", registrationAddress);
ObjectNode actualIdentity = (ObjectNode) actualRegAddress.get("identity");
actualIdentity.put("address", registrationAddress);
}
o.addProperty("registration", registrationNode.toString());
} }
o.addProperty("asleep", client.isAsleep()); o.addProperty("asleep", client.isAsleep());
o.addProperty("lastUplinkTime", client.getLastUplinkTime()); o.addProperty("lastUplinkTime", client.getLastUplinkTime());
@ -138,33 +161,35 @@ public class LwM2MClientSerDes {
ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").getAsString()); ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").getAsString());
if (multiInstances) { if (multiInstances) {
Map<Integer, Object> instances = new HashMap<>(); Map<Integer, Object> instances = new HashMap<>();
o.get("instances").getAsJsonArray().forEach(entry -> { for (Entry<String, JsonElement> entry : o.get("instances").getAsJsonObject().entrySet()) {
// instances.put(Integer.valueOf(entry.getAsJsonObject().), parseValue(type, entry.getValue())); JsonObject instance = entry.getValue().getAsJsonObject();
}); instances.put(Integer.valueOf(instance.get("id").getAsString()), parseValue(type, instance.get(VALUE)));
}
return LwM2mMultipleResource.newResource(id, instances, type); return LwM2mMultipleResource.newResource(id, instances, type);
} else { } else {
return LwM2mSingleResource.newResource(id, parseValue(type, (JsonValue) o.get(VALUE))); return LwM2mSingleResource.newResource(id, parseValue(type, o.get(VALUE)));
} }
} }
private static Object parseValue(ResourceModel.Type type, JsonValue value) {
private static Object parseValue(ResourceModel.Type type, JsonElement value) {
switch (type) { switch (type) {
case INTEGER: case INTEGER:
return value.value(); return value.getAsInt();
// case FLOAT: case FLOAT:
// return value.value(); return value.getAsDouble();
// case BOOLEAN: case BOOLEAN:
// return value.asBoolean(); return value.getAsBoolean();
// case OPAQUE: case OPAQUE:
// return Base64.getDecoder().decode(value.asString()); return Base64.getDecoder().decode(value.getAsString());
// case STRING: case STRING:
// return value.asString(); return value.getAsString();
// case TIME: case TIME:
// return new Date(value.asLong()); return Instant.ofEpochMilli(value.getAsLong());
// case OBJLNK: case OBJLNK:
// return ObjectLink.decodeFromString(value.asString()); return ObjectLink.decodeFromString(value.getAsString());
// case UNSIGNED_INTEGER: case UNSIGNED_INTEGER:
// return ULong.valueOf(value.asString()); return ULong.valueOf(value.getAsString());
default: default:
throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); throw new LwM2mNodeException(String.format("Type %s is not supported", type.name()));
} }
@ -212,7 +237,7 @@ public class LwM2MClientSerDes {
o.addProperty(VALUE, Base64.getEncoder().encodeToString((byte[]) value)); o.addProperty(VALUE, Base64.getEncoder().encodeToString((byte[]) value));
break; break;
case STRING: case STRING:
o.addProperty(VALUE, (String) value); o.addProperty(VALUE, String.valueOf(value));
break; break;
case TIME: case TIME:
o.addProperty(VALUE, ((Date) value).getTime()); o.addProperty(VALUE, ((Date) value).getTime());
@ -221,7 +246,7 @@ public class LwM2MClientSerDes {
o.addProperty(VALUE, ((ObjectLink) value).encodeToString()); o.addProperty(VALUE, ((ObjectLink) value).encodeToString());
break; break;
case UNSIGNED_INTEGER: case UNSIGNED_INTEGER:
o.addProperty(VALUE, value.toString()); o.addProperty(VALUE, Integer.toUnsignedString((int)value));
break; break;
default: default:
throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); throw new LwM2mNodeException(String.format("Type %s is not supported", type.name()));
@ -230,111 +255,111 @@ public class LwM2MClientSerDes {
@SneakyThrows @SneakyThrows
public static LwM2mClient deserialize(byte[] data) { public static LwM2mClient deserialize(byte[] data) {
// JsonObject o = new JsonObject(new String(data))); JsonObject o = JsonParser.parseString(new String(data)).getAsJsonObject();
// LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").getAsString(), o.get("endpoint").getAsString()); LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").getAsString(), o.get("endpoint").getAsString());
LwM2mClient lwM2mClient = new LwM2mClient("nodeId", "endpoint");
// o.get("resources").getAsJsonObject().forEach(entry -> { o.get("resources").getAsJsonObject().entrySet().forEach(entry -> {
// JsonObject resource = entry.getValue().asObject(); JsonObject resource = entry.getValue().getAsJsonObject();
// LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").getAsJsonObject()); LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").getAsJsonObject());
// ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").asObject()); ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").getAsJsonObject());
// ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel); ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel);
// lwM2mClient.getResources().put(entry.getName(), resourceValue); lwM2mClient.getResources().put(String.valueOf(lwM2mResource.getId()), resourceValue);
// }); });
//
// for (JsonObject.Member entry : o.get("sharedAttributes").asObject()) { for (Entry<String, JsonElement> entry : o.get("sharedAttributes").getAsJsonObject().entrySet()) {
// TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder(); TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder();
// JsonFormat.parser().merge(entry.getValue().getAsString(), builder); JsonFormat.parser().merge(entry.getValue().getAsString(), builder);
// lwM2mClient.getSharedAttributes().put(entry.getName(), builder.build()); lwM2mClient.getSharedAttributes().put(entry.getKey(), builder.build());
// } }
//
// o.get("keyTsLatestMap").asObject().forEach(entry -> { o.get("keyTsLatestMap").getAsJsonObject().entrySet().forEach(entry -> {
// lwM2mClient.getKeyTsLatestMap().put(entry.getName(), new AtomicLong(entry.getValue().asLong())); lwM2mClient.getKeyTsLatestMap().put(entry.getKey(), new AtomicLong(entry.getValue().getAsLong()));
// }); });
//
// lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").getAsString())); lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").getAsString()));
//
// Class<LwM2mClient> lwM2mClientClass = LwM2mClient.class; Class<LwM2mClient> lwM2mClientClass = LwM2mClient.class;
//
// JsonValue session = o.get("session"); JsonElement session = o.get("session");
// if (session != null) { if (session != null) {
// TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder(); TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder();
// JsonFormat.parser().merge(session.asString(), builder); JsonFormat.parser().merge(session.getAsString(), builder);
//
// Field sessionField = lwM2mClientClass.getDeclaredField("session"); Field sessionField = lwM2mClientClass.getDeclaredField("session");
// sessionField.setAccessible(true); sessionField.setAccessible(true);
// sessionField.set(lwM2mClient, builder.build()); sessionField.set(lwM2mClient, builder.build());
// } }
//
// JsonValue tenantId = o.get("tenantId"); JsonElement tenantId = o.get("tenantId");
// if (tenantId != null) { if (tenantId != null) {
// Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId"); Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId");
// tenantIdField.setAccessible(true); tenantIdField.setAccessible(true);
// tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.asString()))); tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.getAsString())));
// } }
//
// JsonValue deviceId = o.get("deviceId"); JsonElement deviceId = o.get("deviceId");
// if (tenantId != null) { if (tenantId != null) {
// Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId"); Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId");
// deviceIdField.setAccessible(true); deviceIdField.setAccessible(true);
// deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.asString())); deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.getAsString()));
// } }
//
// JsonValue profileId = o.get("profileId"); JsonElement profileId = o.get("profileId");
// if (tenantId != null) { if (tenantId != null) {
// Field profileIdField = lwM2mClientClass.getDeclaredField("profileId"); Field profileIdField = lwM2mClientClass.getDeclaredField("profileId");
// profileIdField.setAccessible(true); profileIdField.setAccessible(true);
// profileIdField.set(lwM2mClient, UUID.fromString(profileId.asString())); profileIdField.set(lwM2mClient, UUID.fromString(profileId.getAsString()));
// } }
//
// JsonValue powerMode = o.get("powerMode"); JsonElement powerMode = o.get("powerMode");
// if (powerMode != null) { if (powerMode != null) {
// Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode"); Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode");
// powerModeField.setAccessible(true); powerModeField.setAccessible(true);
// powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.asString())); powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.getAsString()));
// } }
//
// JsonValue edrxCycle = o.get("edrxCycle"); JsonElement edrxCycle = o.get("edrxCycle");
// if (edrxCycle != null) { if (edrxCycle != null) {
// Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle"); Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle");
// edrxCycleField.setAccessible(true); edrxCycleField.setAccessible(true);
// edrxCycleField.set(lwM2mClient, edrxCycle.asLong()); edrxCycleField.set(lwM2mClient, edrxCycle.getAsLong());
// } }
//
// JsonValue psmActivityTimer = o.get("psmActivityTimer"); JsonElement psmActivityTimer = o.get("psmActivityTimer");
// if (psmActivityTimer != null) { if (psmActivityTimer != null) {
// Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer"); Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer");
// psmActivityTimerField.setAccessible(true); psmActivityTimerField.setAccessible(true);
// psmActivityTimerField.set(lwM2mClient, psmActivityTimer.asLong()); psmActivityTimerField.set(lwM2mClient, psmActivityTimer.getAsLong());
// } }
//
// JsonValue pagingTransmissionWindow = o.get("pagingTransmissionWindow"); JsonElement pagingTransmissionWindow = o.get("pagingTransmissionWindow");
// if (pagingTransmissionWindow != null) { if (pagingTransmissionWindow != null) {
// Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow"); Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow");
// pagingTransmissionWindowField.setAccessible(true); pagingTransmissionWindowField.setAccessible(true);
// pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.asLong()); pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.getAsLong());
// } }
//
// JsonValue registration = o.get("registration"); JsonElement registration = o.get("registration");
// if (registration != null) { if (registration != null) {
// lwM2mClient.setRegistration(RegistrationSerDes.deserialize(registration.asObject())); lwM2mClient.setRegistration(registrationSerDes.deserialize(toJsonNode(registration.getAsString())));
// } }
//
// lwM2mClient.setAsleep(o.get("asleep").getAsBoolean()); lwM2mClient.setAsleep(o.get("asleep").getAsBoolean());
//
// Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime"); Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime");
// lastUplinkTimeField.setAccessible(true); lastUplinkTimeField.setAccessible(true);
// lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").asLong()); lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").getAsLong());
//
// Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink"); Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink");
// firstEdrxDownlinkField.setAccessible(true); firstEdrxDownlinkField.setAccessible(true);
// firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").getAsBoolean()); firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").getAsBoolean());
//
// lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").asInt()); lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").getAsInt());
//
// JsonValue lastSentRpcId = o.get("lastSentRpcId"); JsonElement lastSentRpcId = o.get("lastSentRpcId");
// if (lastSentRpcId != null) { if (lastSentRpcId != null) {
// lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.asString())); lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.getAsString()));
// } }
return lwM2mClient; return lwM2mClient;
} }

100
common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java

@ -1,100 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.bootstrap;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.junit.Ignore;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.TbLwM2MDtlsBootstrapCertificateVerifier;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.when;
@ExtendWith(MockitoExtension.class)
public class LwM2MTransportBootstrapServiceTest {
@Mock
private LwM2MTransportServerConfig serverConfig;
@Mock
private LwM2MTransportBootstrapConfig bootstrapConfig;
@Mock
private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
@Mock
private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
@Mock
private TransportService transportService;
@Mock
private TbLwM2MDtlsBootstrapCertificateVerifier certificateVerifier;
@Disabled // fixme: nick
@Test
public void getLHServer_creates_ConnectionIdGenerator_when_connection_id_length_not_null(){
final Integer CONNECTION_ID_LENGTH = 6;
when(serverConfig.getDtlsCidLength()).thenReturn(CONNECTION_ID_LENGTH);
var lwM2MBootstrapService = createLwM2MBootstrapService();
var server = lwM2MBootstrapService.getLhBootstrapServer();
var securedEndpoint = (CoapEndpoint) ReflectionTestUtils.getField(server, "securedEndpoint");
assertThat(securedEndpoint).isNotNull();
var config = (DtlsConnectorConfig) ReflectionTestUtils.getField(securedEndpoint.getConnector(), "config");
assertThat(config).isNotNull();
assertThat(config.getConnectionIdGenerator()).isNotNull();
assertThat((Integer) ReflectionTestUtils.getField(config.getConnectionIdGenerator(), "connectionIdLength"))
.isEqualTo(CONNECTION_ID_LENGTH);
}
@Disabled // fixme: nick
@Test
public void getLHServer_creates_no_ConnectionIdGenerator_when_connection_id_length_is_null(){
when(serverConfig.getDtlsCidLength()).thenReturn(null);
var lwM2MBootstrapService = createLwM2MBootstrapService();
var server = lwM2MBootstrapService.getLhBootstrapServer();
var securedEndpoint = (CoapEndpoint) ReflectionTestUtils.getField(server, "securedEndpoint");
assertThat(securedEndpoint).isNotNull();
var config = (DtlsConnectorConfig) ReflectionTestUtils.getField(securedEndpoint.getConnector(), "config");
assertThat(config).isNotNull();
assertThat(config.getConnectionIdGenerator()).isNull();
}
private LwM2MTransportBootstrapService createLwM2MBootstrapService() {
setDefaultConfigVariables();
return new LwM2MTransportBootstrapService(serverConfig, bootstrapConfig, lwM2MBootstrapSecurityStore,
lwM2MInMemoryBootstrapConfigStore, transportService, certificateVerifier);
}
private void setDefaultConfigVariables(){
when(bootstrapConfig.getPort()).thenReturn(5683);
when(bootstrapConfig.getSecurePort()).thenReturn(5684);
when(serverConfig.isRecommendedCiphers()).thenReturn(false);
when(serverConfig.getDtlsRetransmissionTimeout()).thenReturn(9000);
}
}

11
common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java

@ -15,9 +15,10 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.client; package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.core.endpoint.EndpointUriUtil;
import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.Link;
import org.eclipse.leshan.core.peer.IpPeer;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@ -25,14 +26,14 @@ import java.net.InetSocketAddress;
public class LwM2mClientTest { public class LwM2mClientTest {
@Ignore
@Test @Test
public void setRegistration() { public void setRegistration() {
LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint"); LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint");
Registration registration = null; /*new Registration Registration registration = new Registration
.Builder("test", "testEndpoint", Identity.unsecure(new InetSocketAddress(1000))) // FIXME: nick .Builder("testId", "testEndpoint", new IpPeer(new InetSocketAddress(1000)),
EndpointUriUtil.createUri("coap://localhost:5685"))
.objectLinks(new Link[0]) .objectLinks(new Link[0])
.build();*/ .build();
Assertions.assertDoesNotThrow(() -> client.setRegistration(registration)); Assertions.assertDoesNotThrow(() -> client.setRegistration(registration));
} }

41
common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java

@ -15,13 +15,17 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.store.util; package org.thingsboard.server.transport.lwm2m.server.store.util;
import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
import org.eclipse.leshan.core.endpoint.EndpointUriUtil;
import org.eclipse.leshan.core.link.Link;
import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.peer.IpPeer;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
@ -41,9 +45,12 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -56,10 +63,9 @@ import static org.mockito.Mockito.when;
public class LwM2MClientSerDesTest { public class LwM2MClientSerDesTest {
@Ignore
@Test @Test
public void serializeDeserialize() throws Exception { public void serializeDeserialize() throws Exception {
LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint"); LwM2mClient client = new LwM2mClient("nodeId", "endpoint");
TransportDeviceInfo tdi = new TransportDeviceInfo(); TransportDeviceInfo tdi = new TransportDeviceInfo();
tdi.setPowerMode(PowerMode.PSM); tdi.setPowerMode(PowerMode.PSM);
@ -78,13 +84,13 @@ public class LwM2MClientSerDesTest {
client.init(credentialsResponse, UUID.randomUUID()); client.init(credentialsResponse, UUID.randomUUID());
Registration registration = null; // FIXME: nick Registration registration = new Registration
// new Registration.Builder("test", "testEndpoint", Identity .Builder("test", "endpoint", new IpPeer(new InetSocketAddress(Inet4Address.getLoopbackAddress(), 1000)),
// .unsecure(new InetSocketAddress(1000))) EndpointUriUtil.createUri("coap://localhost:5685"))
// .supportedContentFormats() .supportedContentFormats()
// .supportedObjects(Map.of(15, "1.0", 17, "1.0")) .supportedObjects(Map.of(15, LwM2mVersion.V1_0, 17, LwM2mVersion.V1_0))
// .objectLinks(new Link[]{new Link("/")}) .objectLinks(new Link[] { new Link("/15"), new Link("/17") })
// .build(); .build();
client.setRegistration(registration); client.setRegistration(registration);
client.setState(LwM2MClientState.REGISTERED); client.setState(LwM2MClientState.REGISTERED);
@ -130,7 +136,12 @@ public class LwM2MClientSerDesTest {
assertEquals(client.getPsmActivityTimer(), desClient.getPsmActivityTimer()); assertEquals(client.getPsmActivityTimer(), desClient.getPsmActivityTimer());
assertEquals(client.getPagingTransmissionWindow(), desClient.getPagingTransmissionWindow()); assertEquals(client.getPagingTransmissionWindow(), desClient.getPagingTransmissionWindow());
assertEquals(client.getEdrxCycle(), desClient.getEdrxCycle()); assertEquals(client.getEdrxCycle(), desClient.getEdrxCycle());
assertEquals(client.getRegistration(), desClient.getRegistration()); if (((IpPeer)desClient.getRegistration().getClientTransportData()).getSocketAddress().isUnresolved()) {
String actualReg = desClient.getRegistration().toString().replaceAll("/<unresolved>", "");
assertEquals(client.getRegistration().toString(), actualReg);
} else {
assertEquals(client.getRegistration(), desClient.getRegistration());
}
assertEquals(client.isAsleep(), desClient.isAsleep()); assertEquals(client.isAsleep(), desClient.isAsleep());
assertEquals(client.getLastUplinkTime(), desClient.getLastUplinkTime()); assertEquals(client.getLastUplinkTime(), desClient.getLastUplinkTime());
assertEquals(client.getSleepTask(), desClient.getSleepTask()); assertEquals(client.getSleepTask(), desClient.getSleepTask());
@ -143,7 +154,11 @@ public class LwM2MClientSerDesTest {
Map<String, ResourceValue> actualResources = desClient.getResources(); Map<String, ResourceValue> actualResources = desClient.getResources();
assertNotNull(actualResources); assertNotNull(actualResources);
assertEquals(expectedResources.size(), actualResources.size()); assertEquals(expectedResources.size(), actualResources.size());
expectedResources.forEach((key, value) -> assertEquals(value.toString(), actualResources.get(key).toString())); for (Entry entry : expectedResources.entrySet()) {
LwM2mPath expectedPathId = client.getLwM2mPathFromString(entry.getKey().toString());
String actualOld = actualResources.get(String.valueOf(expectedPathId.getObjectId())).toString();
String actual = actualOld.replaceAll("\"", "");
assertEquals(entry.getValue().toString(), actual);
}
} }
} }

16
common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java

@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
@ -50,6 +51,7 @@ import java.util.regex.Pattern;
/** /**
* Created by Valerii Sosliuk on 5/12/2017. * Created by Valerii Sosliuk on 5/12/2017.
*/ */
@Slf4j
public class JacksonUtil { public class JacksonUtil {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@ -158,6 +160,20 @@ public class JacksonUtil {
} }
} }
public static String toPlainText(String data) {
if (data == null) {
return null;
}
if (data.startsWith("\"") && data.endsWith("\"") && data.length() >= 2) {
final String dataBefore = data;
try {
data = JacksonUtil.fromString(data, String.class);
} catch (Exception ignored) {}
log.trace("Trimming double quotes. Before trim: [{}], after trim: [{}]", dataBefore, data);
}
return data;
}
public static <T> T treeToValue(JsonNode node, Class<T> clazz) { public static <T> T treeToValue(JsonNode node, Class<T> clazz) {
try { try {
return OBJECT_MAPPER.treeToValue(node, clazz); return OBJECT_MAPPER.treeToValue(node, clazz);

14
common/util/src/test/java/org/thingsboard/common/util/JacksonUtilTest.java

@ -19,6 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
@ -55,4 +58,15 @@ public class JacksonUtilTest {
Assert.assertEquals(asset.getName(), result.getName()); Assert.assertEquals(asset.getName(), result.getName());
Assert.assertEquals(asset.getType(), result.getType()); Assert.assertEquals(asset.getType(), result.getType());
} }
@ParameterizedTest
@ValueSource(strings = { "", "false", "\"", "\"\"", "\"This is a string with double quotes\"", "Path: /home/developer/test.txt",
"First line\nSecond line\n\nFourth line", "Before\rAfter", "Tab\tSeparated\tValues", "Test\bbackspace", "[]",
"[1, 2, 3]", "{\"key\": \"value\"}", "{\n\"temperature\": 25.5,\n\"humidity\": 50.2\n\"}", "Expression: (a + b) * c",
"世界", "Україна", "\u1F1FA\u1F1E6", "🇺🇦"})
public void toPlainTextTest(String original) {
String serialized = JacksonUtil.toString(original);
Assertions.assertNotNull(serialized);
Assertions.assertEquals(original, JacksonUtil.toPlainText(serialized));
}
} }

9
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -200,10 +201,10 @@ public class AuditLogServiceImpl implements AuditLogService {
break; break;
case ATTRIBUTES_UPDATED: case ATTRIBUTES_UPDATED:
actionData.put("entityId", entityId.toString()); actionData.put("entityId", entityId.toString());
String scope = extractParameter(String.class, 0, additionalInfo); AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
actionData.put("scope", scope); actionData.put("scope", scope.name());
ObjectNode attrsNode = JacksonUtil.newObjectNode(); ObjectNode attrsNode = JacksonUtil.newObjectNode();
if (attributes != null) { if (attributes != null) {
for (AttributeKvEntry attr : attributes) { for (AttributeKvEntry attr : attributes) {
@ -215,8 +216,8 @@ public class AuditLogServiceImpl implements AuditLogService {
case ATTRIBUTES_DELETED: case ATTRIBUTES_DELETED:
case ATTRIBUTES_READ: case ATTRIBUTES_READ:
actionData.put("entityId", entityId.toString()); actionData.put("entityId", entityId.toString());
scope = extractParameter(String.class, 0, additionalInfo); scope = extractParameter(AttributeScope.class, 0, additionalInfo);
actionData.put("scope", scope); actionData.put("scope", scope.name());
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<String> keys = extractParameter(List.class, 1, additionalInfo); List<String> keys = extractParameter(List.class, 1, additionalInfo);
ArrayNode attrsArrayNode = actionData.putArray("attributes"); ArrayNode attrsArrayNode = actionData.putArray("attributes");

17
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -21,6 +21,7 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
@ -31,7 +32,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -71,7 +72,6 @@ import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import jakarta.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -354,7 +354,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
return Futures.successfulAsList(futures); return Futures.successfulAsList(futures);
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
edges = Futures.transform(edges, new Function<List<Edge>, List<Edge>>() { edges = Futures.transform(edges, new Function<>() {
@Nullable @Nullable
@Override @Override
public List<Edge> apply(@Nullable List<Edge> edgeList) { public List<Edge> apply(@Nullable List<Edge> edgeList) {
@ -414,7 +414,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
} }
private PaginatedRemover<TenantId, Edge> tenantEdgesRemover = private PaginatedRemover<TenantId, Edge> tenantEdgesRemover =
new PaginatedRemover<TenantId, Edge>() { new PaginatedRemover<>() {
@Override @Override
protected PageData<Edge> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { protected PageData<Edge> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
@ -427,7 +427,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
} }
}; };
private PaginatedRemover<CustomerId, Edge> customerEdgeUnassigner = new PaginatedRemover<CustomerId, Edge>() { private PaginatedRemover<CustomerId, Edge> customerEdgeUnassigner = new PaginatedRemover<>() {
@Override @Override
protected PageData<Edge> findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { protected PageData<Edge> findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) {
@ -513,7 +513,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
@Override @Override
public String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId, String tbRuleChainInputNodeClassName) { public String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId, String tbRuleChainInputNodeClassName) {
List<RuleChain> edgeRuleChains = findEdgeRuleChains(tenantId, edgeId); List<RuleChain> edgeRuleChains = findEdgeRuleChains(tenantId, edgeId);
List<RuleChainId> edgeRuleChainIds = edgeRuleChains.stream().map(IdBased::getId).collect(Collectors.toList()); List<RuleChainId> edgeRuleChainIds = edgeRuleChains.stream().map(IdBased::getId).toList();
ObjectNode result = JacksonUtil.newObjectNode(); ObjectNode result = JacksonUtil.newObjectNode();
for (RuleChain edgeRuleChain : edgeRuleChains) { for (RuleChain edgeRuleChain : edgeRuleChains) {
List<RuleNode> ruleNodes = List<RuleNode> ruleNodes =
@ -522,8 +522,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
List<RuleChainId> connectedRuleChains = List<RuleChainId> connectedRuleChains =
ruleNodes.stream() ruleNodes.stream()
.filter(rn -> rn.getType().equals(tbRuleChainInputNodeClassName)) .filter(rn -> rn.getType().equals(tbRuleChainInputNodeClassName))
.map(rn -> new RuleChainId(UUID.fromString(rn.getConfiguration().get("ruleChainId").asText()))) .map(rn -> new RuleChainId(UUID.fromString(rn.getConfiguration().get("ruleChainId").asText()))).toList();
.collect(Collectors.toList());
List<String> missingRuleChains = new ArrayList<>(); List<String> missingRuleChains = new ArrayList<>();
for (RuleChainId connectedRuleChain : connectedRuleChains) { for (RuleChainId connectedRuleChain : connectedRuleChains) {
if (!edgeRuleChainIds.contains(connectedRuleChain)) { if (!edgeRuleChainIds.contains(connectedRuleChain)) {
@ -549,7 +548,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
if (persistToTelemetry) { if (persistToTelemetry) {
futureKvEntry = timeseriesService.findLatest(tenantId, edgeId, key); futureKvEntry = timeseriesService.findLatest(tenantId, edgeId, key);
} else { } else {
futureKvEntry = attributesService.find(tenantId, edgeId, DataConstants.SERVER_SCOPE, key); futureKvEntry = attributesService.find(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key);
} }
return Futures.transformAsync(futureKvEntry, kvEntryOpt -> return Futures.transformAsync(futureKvEntry, kvEntryOpt ->
Futures.immediateFuture(kvEntryOpt.flatMap(KvEntry::getBooleanValue).orElse(false)), MoreExecutors.directExecutor()); Futures.immediateFuture(kvEntryOpt.flatMap(KvEntry::getBooleanValue).orElse(false)), MoreExecutors.directExecutor());

121
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -40,11 +40,16 @@ import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityFilterType; import org.thingsboard.server.common.data.query.EntityFilterType;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.RelationsQueryFilter; import org.thingsboard.server.common.data.query.RelationsQueryFilter;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -63,6 +68,10 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final CustomerId NULL_CUSTOMER_ID = new CustomerId(NULL_UUID); public static final CustomerId NULL_CUSTOMER_ID = new CustomerId(NULL_UUID);
private static final int MAX_ENTITY_IDS_SIZE = 1024;
private static final Set<EntityFilterType> EXCLUDED_TYPES_FROM_OPTIMIZATION = Set.of(
EntityFilterType.ENTITY_LIST, EntityFilterType.SINGLE_ENTITY, EntityFilterType.RELATIONS_QUERY);
@Autowired @Autowired
private EntityQueryDao entityQueryDao; private EntityQueryDao entityQueryDao;
@ -86,9 +95,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id); validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityDataQuery(query); validateEntityDataQuery(query);
if (EntityFilterType.RELATIONS_QUERY.equals(query.getEntityFilter().getType()) if (!isValidForOptimization(query)) {
|| EntityFilterType.SINGLE_ENTITY.equals(query.getEntityFilter().getType())
|| StringUtils.isNotEmpty(query.getPageLink().getTextSearch())) {
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, query); return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, query);
} }
@ -97,41 +104,10 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
if (entityDataByQuery == null || entityDataByQuery.getData().isEmpty()) { if (entityDataByQuery == null || entityDataByQuery.getData().isEmpty()) {
return entityDataByQuery; return entityDataByQuery;
} }
// 2 step - find entity data by entity ids from the 1st step
PageData<EntityData> result = findEntityDataByEntityIds(tenantId, customerId, query, entityDataByQuery.getData());
return new PageData<>(result.getData(), entityDataByQuery.getTotalPages(), entityDataByQuery.getTotalElements(), entityDataByQuery.hasNext());
}
private PageData<EntityData> findEntityIdsByFilterAndSorterColumns(TenantId tenantId, CustomerId customerId, EntityDataQuery query) {
List<EntityKey> entityFields = null;
List<EntityKey> latestValues = null;
if (query.getPageLink().getSortOrder() != null) {
if (query.getEntityFields() != null) {
entityFields = query.getEntityFields().stream()
.filter(entityKey -> entityKey.getKey().equals(query.getPageLink().getSortOrder().getKey().getKey()))
.collect(Collectors.toList());
}
if (query.getLatestValues() != null) {
latestValues = query.getLatestValues().stream()
.filter(entityKey -> entityKey.getKey().equals(query.getPageLink().getSortOrder().getKey().getKey()))
.collect(Collectors.toList());
}
}
EntityDataQuery entityQuery = new EntityDataQuery(query.getEntityFilter(), query.getPageLink(), entityFields, latestValues, query.getKeyFilters());
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, entityQuery);
}
private PageData<EntityData> findEntityDataByEntityIds(TenantId tenantId, CustomerId customerId, EntityDataQuery query, List<EntityData> data) {
List<String> entityIds = data.stream().map(d -> d.getEntityId().getId().toString()).toList();
EntityType entityType = data.isEmpty() ? null : data.get(0).getEntityId().getEntityType();
EntityListFilter filter = new EntityListFilter();
filter.setEntityType(entityType);
filter.setEntityList(entityIds);
EntityDataPageLink pageLink = new EntityDataPageLink(query.getPageLink().getPageSize(), 0, null, query.getPageLink().getSortOrder()); // 2 step - find entity data by entity ids from the 1st step
EntityDataQuery entityQuery = new EntityDataQuery(filter, pageLink, query.getEntityFields(), query.getLatestValues(), null); List<EntityData> result = fetchEntityDataByIdsFromInitialQuery(tenantId, customerId, query, entityDataByQuery.getData());
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, entityQuery); return new PageData<>(result, entityDataByQuery.getTotalPages(), entityDataByQuery.getTotalElements(), entityDataByQuery.hasNext());
} }
@Override @Override
@ -228,4 +204,75 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
} }
} }
private boolean isValidForOptimization(EntityDataQuery query) {
if (StringUtils.isNotEmpty(query.getPageLink().getTextSearch())) {
return false;
}
if (EXCLUDED_TYPES_FROM_OPTIMIZATION.contains(query.getEntityFilter().getType())) {
return false;
}
if ((query.getEntityFields() == null || query.getEntityFields().isEmpty()) &&
(query.getLatestValues() == null || query.getLatestValues().isEmpty())) {
return false;
}
Set<EntityKey> filteringKeys = new HashSet<>(Optional.ofNullable(query.getKeyFilters()).orElse(Collections.emptyList()).stream().map(KeyFilter::getKey).toList());
Set<EntityKey> entityFields = new HashSet<>(Optional.ofNullable(query.getEntityFields()).orElse(Collections.emptyList()));
Set<EntityKey> latestValues = new HashSet<>(Optional.ofNullable(query.getLatestValues()).orElse(Collections.emptyList()));
return !(filteringKeys.containsAll(entityFields) && filteringKeys.containsAll(latestValues));
}
private PageData<EntityData> findEntityIdsByFilterAndSorterColumns(TenantId tenantId, CustomerId customerId, EntityDataQuery query) {
List<EntityKey> entityFields = null;
List<EntityKey> latestValues = null;
if (query.getPageLink().getSortOrder() != null) {
if (query.getEntityFields() != null) {
entityFields = query.getEntityFields().stream()
.filter(entityKey -> entityKey.getKey().equals(query.getPageLink().getSortOrder().getKey().getKey()))
.collect(Collectors.toList());
}
if (query.getLatestValues() != null) {
latestValues = query.getLatestValues().stream()
.filter(entityKey -> entityKey.getKey().equals(query.getPageLink().getSortOrder().getKey().getKey()))
.collect(Collectors.toList());
}
}
EntityDataQuery entityQuery = new EntityDataQuery(query.getEntityFilter(), query.getPageLink(), entityFields, latestValues, query.getKeyFilters());
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, entityQuery);
}
private List<EntityData> fetchEntityDataByIdsFromInitialQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query, List<EntityData> initialQueryResult) {
List<EntityData> result = new ArrayList<>();
List<String> entityIds = initialQueryResult.stream().map(d -> d.getEntityId().getId().toString()).collect(Collectors.toList());
EntityType entityType = initialQueryResult.get(0).getEntityId().getEntityType();
if (entityIds.size() > MAX_ENTITY_IDS_SIZE) {
List<List<String>> chunks = new ArrayList<>();
for (int i = 0; i < entityIds.size(); i += MAX_ENTITY_IDS_SIZE) {
chunks.add(entityIds.subList(i, Math.min(entityIds.size(), i + MAX_ENTITY_IDS_SIZE)));
}
for (List<String> chunk : chunks) {
result.addAll(findEntityDataByEntityIds(tenantId, customerId, query, chunk, entityType, chunk.size()));
}
} else {
result.addAll(findEntityDataByEntityIds(tenantId, customerId, query, entityIds, entityType, query.getPageLink().getPageSize()));
}
return result;
}
private List<EntityData> findEntityDataByEntityIds(TenantId tenantId, CustomerId customerId, EntityDataQuery query,
List<String> entityIds, EntityType entityType, int pageSize) {
EntityListFilter filter = new EntityListFilter();
filter.setEntityType(entityType);
filter.setEntityList(entityIds);
EntityDataPageLink pageLink = new EntityDataPageLink(pageSize, 0, null, query.getPageLink().getSortOrder());
EntityDataQuery entityQuery = new EntityDataQuery(filter, pageLink, query.getEntityFields(), query.getLatestValues(), null);
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, entityQuery).getData();
}
} }

10
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -106,6 +107,11 @@ public class BaseEventService implements EventService {
return convert(entityId.getEntityType(), eventDao.findLatestEvents(tenantId.getId(), entityId.getId(), eventType, limit)); return convert(entityId.getEntityType(), eventDao.findLatestEvents(tenantId.getId(), entityId.getId(), eventType, limit));
} }
@Override
public EventInfo findLatestDebugRuleNodeInEvent(TenantId tenantId, EntityId entityId) {
return convert(entityId.getEntityType(), eventDao.findLatestDebugRuleNodeInEvent(tenantId.getId(), entityId.getId()));
}
@Override @Override
public PageData<EventInfo> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) { public PageData<EventInfo> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) {
return convert(entityId.getEntityType(), eventDao.findEventByFilter(tenantId.getId(), entityId.getId(), eventFilter, pageLink)); return convert(entityId.getEntityType(), eventDao.findEventByFilter(tenantId.getId(), entityId.getId(), eventFilter, pageLink));
@ -140,4 +146,8 @@ public class BaseEventService implements EventService {
return list == null ? null : list.stream().map(e -> e.toInfo(entityType)).collect(Collectors.toList()); return list == null ? null : list.stream().map(e -> e.toInfo(entityType)).collect(Collectors.toList());
} }
private EventInfo convert(EntityType entityType, Event event) {
return Optional.ofNullable(event).map(e -> e.toInfo(entityType)).orElse(null);
}
} }

9
dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java

@ -62,6 +62,15 @@ public interface EventDao {
*/ */
List<? extends Event> findLatestEvents(UUID tenantId, UUID entityId, EventType eventType, int limit); List<? extends Event> findLatestEvents(UUID tenantId, UUID entityId, EventType eventType, int limit);
/**
* Find latest debug IN event by tenantId, entityId.
*
* @param tenantId the tenantId
* @param entityId the entityId
* @return the latest debug IN event
*/
Event findLatestDebugRuleNodeInEvent(UUID tenantId, UUID entityId);
/** /**
* Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events.
* @param regularEventExpTs the expiration time of the regular events * @param regularEventExpTs the expiration time of the regular events

3
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
@ -157,7 +156,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
return new UserNotificationSettings(prefs); return new UserNotificationSettings(prefs);
} }
@Transactional(propagation = Propagation.NOT_SUPPORTED) // so that parent transaction is not aborted on method failure @Transactional
@Override @Override
public void createDefaultNotificationConfigs(TenantId tenantId) { public void createDefaultNotificationConfigs(TenantId tenantId) {
NotificationTarget allUsers = createTarget(tenantId, "All users", new AllUsersFilter(), NotificationTarget allUsers = createTarget(tenantId, "All users", new AllUsersFilter(),

2
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -119,7 +119,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
public RuleChain saveRuleChain(RuleChain ruleChain, boolean publishSaveEvent) { public RuleChain saveRuleChain(RuleChain ruleChain, boolean publishSaveEvent) {
ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);
try { try {
RuleChain savedRuleChain = ruleChainDao.save(ruleChain.getTenantId(), ruleChain); RuleChain savedRuleChain = ruleChainDao.saveAndFlush(ruleChain.getTenantId(), ruleChain);
if (ruleChain.getId() == null) { if (ruleChain.getId() == null) {
entityCountService.publishCountEntityEvictEvent(ruleChain.getTenantId(), EntityType.RULE_CHAIN); entityCountService.publishCountEntityEvictEvent(ruleChain.getTenantId(), EntityType.RULE_CHAIN);
} }

5
dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java

@ -392,6 +392,11 @@ public class JpaBaseEventDao implements EventDao {
return DaoUtil.convertDataList(getEventRepository(eventType).findLatestEvents(tenantId, entityId, limit)); return DaoUtil.convertDataList(getEventRepository(eventType).findLatestEvents(tenantId, entityId, limit));
} }
@Override
public Event findLatestDebugRuleNodeInEvent(UUID tenantId, UUID entityId) {
return DaoUtil.getData(ruleNodeDebugEventRepository.findLatestDebugRuleNodeInEvent(tenantId, entityId));
}
@Override @Override
public void cleanupEvents(long regularEventExpTs, long debugEventExpTs, boolean cleanupDb) { public void cleanupEvents(long regularEventExpTs, long debugEventExpTs, boolean cleanupDb) {
if (regularEventExpTs > 0) { if (regularEventExpTs > 0) {

4
dao/src/main/java/org/thingsboard/server/dao/sql/event/RuleNodeDebugEventRepository.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.event.RuleNodeDebugEvent;
import org.thingsboard.server.dao.model.sql.RuleNodeDebugEventEntity; import org.thingsboard.server.dao.model.sql.RuleNodeDebugEventEntity;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -35,6 +36,9 @@ public interface RuleNodeDebugEventRepository extends EventRepository<RuleNodeDe
@Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId ORDER BY e.ts DESC LIMIT :limit") @Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId ORDER BY e.ts DESC LIMIT :limit")
List<RuleNodeDebugEventEntity> findLatestEvents(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId, @Param("limit") int limit); List<RuleNodeDebugEventEntity> findLatestEvents(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId, @Param("limit") int limit);
@Query(nativeQuery = true, value = "SELECT * FROM rule_node_debug_event e WHERE e.tenant_id = :tenantId AND e.entity_id = :entityId AND e.e_type = 'IN' ORDER BY e.ts DESC LIMIT 1")
Optional<RuleNodeDebugEventEntity> findLatestDebugRuleNodeInEvent(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId);
@Override @Override
@Query("SELECT e FROM RuleNodeDebugEventEntity e WHERE " + @Query("SELECT e FROM RuleNodeDebugEventEntity e WHERE " +
"e.tenantId = :tenantId " + "e.tenantId = :tenantId " +

31
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -64,6 +64,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -192,12 +193,12 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
@Override @Override
@Transactional @Transactional
public Tenant saveTenant(Tenant tenant) { public Tenant saveTenant(Tenant tenant) {
return saveTenant(tenant, true); return saveTenant(tenant, null);
} }
@Override @Override
@Transactional @Transactional
public Tenant saveTenant(Tenant tenant, boolean publishSaveEvent) { public Tenant saveTenant(Tenant tenant, Consumer<TenantId> defaultEntitiesCreator) {
log.trace("Executing saveTenant [{}]", tenant); log.trace("Executing saveTenant [{}]", tenant);
tenant.setRegion(DEFAULT_TENANT_REGION); tenant.setRegion(DEFAULT_TENANT_REGION);
if (tenant.getTenantProfileId() == null) { if (tenant.getTenantProfileId() == null) {
@ -206,20 +207,20 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
} }
tenantValidator.validate(tenant, Tenant::getId); tenantValidator.validate(tenant, Tenant::getId);
boolean create = tenant.getId() == null; boolean create = tenant.getId() == null;
Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); Tenant savedTenant = tenantDao.save(tenant.getId(), tenant);
publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), create)); TenantId tenantId = savedTenant.getId();
if (publishSaveEvent) { publishEvictEvent(new TenantEvictEvent(tenantId, create));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedTenant.getId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(savedTenant.getId()).entity(savedTenant).created(create).build()); .entityId(tenantId).entity(savedTenant).created(create).build());
}
if (tenant.getId() == null) { if (create) {
deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); deviceProfileService.createDefaultDeviceProfile(tenantId);
assetProfileService.createDefaultAssetProfile(savedTenant.getId()); assetProfileService.createDefaultAssetProfile(tenantId);
apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null); apiUsageStateService.createDefaultApiUsageState(tenantId, null);
try { notificationSettingsService.createDefaultNotificationConfigs(tenantId);
notificationSettingsService.createDefaultNotificationConfigs(savedTenant.getId()); if (defaultEntitiesCreator != null) {
} catch (Throwable e) { defaultEntitiesCreator.accept(tenantId);
log.error("Failed to create default notification configs for tenant {}", savedTenant.getId(), e);
} }
} }
return savedTenant; return savedTenant;

5
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -98,6 +98,10 @@ public abstract class AbstractServiceTest {
protected RuleNodeDebugEvent generateEvent(TenantId tenantId, EntityId entityId) throws IOException { protected RuleNodeDebugEvent generateEvent(TenantId tenantId, EntityId entityId) throws IOException {
return generateEvent(tenantId, entityId, null);
}
protected RuleNodeDebugEvent generateEvent(TenantId tenantId, EntityId entityId, String eventType) throws IOException {
if (tenantId == null) { if (tenantId == null) {
tenantId = TenantId.fromUUID(Uuids.timeBased()); tenantId = TenantId.fromUUID(Uuids.timeBased());
} }
@ -105,6 +109,7 @@ public abstract class AbstractServiceTest {
.tenantId(tenantId) .tenantId(tenantId)
.entityId(entityId.getId()) .entityId(entityId.getId())
.serviceId("server A") .serviceId("server A")
.eventType(eventType)
.data(JacksonUtil.toString(readFromResource("TestJsonData.json"))) .data(JacksonUtil.toString(readFromResource("TestJsonData.json")))
.build(); .build();
} }

129
dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java

@ -26,7 +26,6 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.ResultSetExtractor;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -99,6 +98,8 @@ import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.common.data.query.EntityKeyType.ATTRIBUTE;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
@ -2113,6 +2114,132 @@ public class EntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
} }
@Test
public void testFindEntityQuery_for_5000_devices_with_3000_pageSize() {
int pageSize = 3000;
int expectedDevicesSize = 4000;
int unexpectedDevicesSize = 1000;
for (int i = 0; i < expectedDevicesSize + unexpectedDevicesSize; i++) {
Device device = new Device();
device.setTenantId(tenantId);
if (i < expectedDevicesSize) {
device.setName("Device_" + i); // match deviceNameFilter 'D%'
} else {
device.setName("Test_" + i); // does not match deviceNameFilter 'D%'
}
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
Device savedDevice = deviceService.saveDevice(device);
attributesService.save(tenantId, savedDevice.getId(), AttributeScope.CLIENT_SCOPE,
new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("telemetry", (long) i)));
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("D%");
EntityDataSortOrder sortOrder = new EntityDataSortOrder(new EntityKey(ATTRIBUTE, "telemetry"), EntityDataSortOrder.Direction.DESC);
List<KeyFilter> deviceTypeFilters = createStringKeyFilters("type", ENTITY_FIELD, StringFilterPredicate.StringOperation.EQUAL, "default");
List<KeyFilter> attributeFilters = Collections.singletonList(createNumericKeyFilter("telemetry", ATTRIBUTE, NumericFilterPredicate.NumericOperation.LESS, expectedDevicesSize));
List<KeyFilter> nameFilters = createStringKeyFilters("name", ENTITY_FIELD, StringFilterPredicate.StringOperation.CONTAINS, "Device");
List<EntityKey> entityFields = Arrays.asList(new EntityKey(ENTITY_FIELD, "name"), new EntityKey(ENTITY_FIELD, "type"));
// 1. Device type filters:
// query with textSearch - optimization is not performing
EntityDataPageLink originalPageLink = new EntityDataPageLink(pageSize, 0, "Device", sortOrder);
EntityDataQuery originalQuery = new EntityDataQuery(filter, originalPageLink, entityFields, null, deviceTypeFilters);
PageData<EntityData> originalData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), originalQuery);
// query without textSearch - optimization is performing
EntityDataPageLink optimizedPageLink = new EntityDataPageLink(pageSize, 0, null, sortOrder);
EntityDataQuery optimizedQuery = new EntityDataQuery(filter, optimizedPageLink, entityFields, null, deviceTypeFilters);
PageData<EntityData> optimizedData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), optimizedQuery);
List<EntityData> loadedEntities = getLoadedEntities(optimizedData, optimizedQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
loadedEntities = getLoadedEntities(originalData, originalQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
Assert.assertEquals(pageSize, optimizedData.getData().size());
for (int i = 0; i < pageSize; i++) {
EntityData originalElement = originalData.getData().get(i);
EntityData optimizedElement = optimizedData.getData().get(i);
Assert.assertEquals(originalElement.getEntityId(), optimizedElement.getEntityId());
originalElement.getLatest().get(ENTITY_FIELD).forEach((key, value) -> {
Assert.assertEquals(value.getValue(), optimizedElement.getLatest().get(EntityKeyType.ENTITY_FIELD).get(key).getValue());
Assert.assertEquals(value.getCount(), optimizedElement.getLatest().get(EntityKeyType.ENTITY_FIELD).get(key).getCount());
});
}
Assert.assertEquals(originalData.getTotalPages(), optimizedData.getTotalPages());
Assert.assertEquals(originalData.getTotalElements(), optimizedData.getTotalElements());
// 2. Device attribute filters
// query with textSearch - optimization is not performing
originalPageLink = new EntityDataPageLink(pageSize, 0, "Device", sortOrder);
originalQuery = new EntityDataQuery(filter, originalPageLink, entityFields, null, attributeFilters);
originalData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), originalQuery);
// query without textSearch - optimization is performing
optimizedPageLink = new EntityDataPageLink(pageSize, 0, null, sortOrder);
optimizedQuery = new EntityDataQuery(filter, optimizedPageLink, entityFields, null, attributeFilters);
optimizedData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), optimizedQuery);
loadedEntities = getLoadedEntities(optimizedData, optimizedQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
loadedEntities = getLoadedEntities(originalData, originalQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
Assert.assertEquals(pageSize, optimizedData.getData().size());
for (int i = 0; i < pageSize; i++) {
EntityData originalElement = originalData.getData().get(i);
EntityData optimizedElement = optimizedData.getData().get(i);
Assert.assertEquals(originalElement.getEntityId(), optimizedElement.getEntityId());
originalElement.getLatest().get(ENTITY_FIELD).forEach((key, value) -> {
Assert.assertEquals(value.getValue(), optimizedElement.getLatest().get(EntityKeyType.ENTITY_FIELD).get(key).getValue());
Assert.assertEquals(value.getCount(), optimizedElement.getLatest().get(EntityKeyType.ENTITY_FIELD).get(key).getCount());
});
}
Assert.assertEquals(originalData.getTotalPages(), optimizedData.getTotalPages());
Assert.assertEquals(originalData.getTotalElements(), optimizedData.getTotalElements());
// 3. Device name filters
// query with textSearch - optimization is not performing
originalPageLink = new EntityDataPageLink(pageSize, 0, "Device", sortOrder);
originalQuery = new EntityDataQuery(filter, originalPageLink, entityFields, null, nameFilters);
originalData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), originalQuery);
// query without textSearch - optimization is performing
optimizedPageLink = new EntityDataPageLink(pageSize, 0, null, sortOrder);
optimizedQuery = new EntityDataQuery(filter, optimizedPageLink, entityFields, null, nameFilters);
optimizedData = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), optimizedQuery);
loadedEntities = getLoadedEntities(optimizedData, optimizedQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
loadedEntities = getLoadedEntities(originalData, originalQuery);
Assert.assertEquals(expectedDevicesSize, loadedEntities.size());
Assert.assertEquals(pageSize, optimizedData.getData().size());
for (int i = 0; i < pageSize; i++) {
EntityData originalElement = originalData.getData().get(i);
EntityData optimizedElement = optimizedData.getData().get(i);
Assert.assertEquals(originalElement.getEntityId(), optimizedElement.getEntityId());
originalElement.getLatest().get(ENTITY_FIELD).forEach((key, value) -> {
Assert.assertEquals(value.getValue(), optimizedElement.getLatest().get(EntityKeyType.ENTITY_FIELD).get(key).getValue());
Assert.assertEquals(value.getCount(), optimizedElement.getLatest().get(EntityKeyType.ENTITY_FIELD).get(key).getCount());
});
}
Assert.assertEquals(originalData.getTotalPages(), optimizedData.getTotalPages());
Assert.assertEquals(originalData.getTotalElements(), optimizedData.getTotalElements());
deviceService.deleteDevicesByTenantId(tenantId);
}
private Boolean listEqualWithoutOrder(List<String> A, List<String> B) { private Boolean listEqualWithoutOrder(List<String> A, List<String> B) {
return A.containsAll(B) && B.containsAll(A); return A.containsAll(B) && B.containsAll(A);
} }

34
dao/src/test/java/org/thingsboard/server/dao/service/RuleChainServiceTest.java

@ -40,8 +40,11 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** /**
* Created by igor on 3/13/18. * Created by igor on 3/13/18.
*/ */
@ -560,4 +563,35 @@ public class RuleChainServiceTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@Test
public void testSaveRuleChainWithExistingExternalId() {
RuleChainId externalRuleChainId = new RuleChainId(UUID.fromString("2675d180-e1e5-11ee-9f06-71b6c7dc2cbf"));
RuleChain ruleChain = getRuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setExternalId(externalRuleChainId);
ruleChainService.saveRuleChain(ruleChain);
assertThatThrownBy(() -> ruleChainService.saveRuleChain(ruleChain))
.isInstanceOf(DataValidationException.class)
.hasMessage("Rule Chain with such external id already exists!");
ruleChainService.deleteRuleChainsByTenantId(tenantId);
}
private RuleChain getRuleChain() {
String ruleChainStr = "{\n" +
" \"name\": \"Root Rule Chain\",\n" +
" \"type\": \"CORE\",\n" +
" \"firstRuleNodeId\": {\n" +
" \"entityType\": \"RULE_NODE\",\n" +
" \"id\": \"91ad0b00-e779-11ee-9cf0-15d8b6079fdb\"\n" +
" },\n" +
" \"debugMode\": false,\n" +
" \"configuration\": null,\n" +
" \"additionalInfo\": null\n" +
"}";
return JacksonUtil.fromString(ruleChainStr, RuleChain.class);
}
} }

22
dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java

@ -132,8 +132,28 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest {
eventService.cleanupEvents(timeBeforeStartTime - 1, timeAfterEndTime + 1, true); eventService.cleanupEvents(timeBeforeStartTime - 1, timeAfterEndTime + 1, true);
} }
@Test
public void findLatestDebugRuleNodeInEvent() throws Exception {
CustomerId customerId = new CustomerId(Uuids.timeBased());
TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
Event event1 = saveEventWithProvidedTimeAndEventType(eventTime, "IN", customerId, tenantId);
Event event2 = saveEventWithProvidedTimeAndEventType(eventTime + 1, "IN", customerId, tenantId);
EventInfo event = eventService.findLatestDebugRuleNodeInEvent(tenantId, customerId);
Assert.assertNotNull(event);
Assert.assertEquals(event2.getUuidId(), event.getUuidId());
eventService.cleanupEvents(timeBeforeStartTime - 1, timeAfterEndTime + 1, true);
}
private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws Exception { private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws Exception {
RuleNodeDebugEvent event = generateEvent(tenantId, entityId); return saveEventWithProvidedTimeAndEventType(time, null, entityId, tenantId);
}
private Event saveEventWithProvidedTimeAndEventType(long time, String eventType, EntityId entityId, TenantId tenantId) throws Exception {
RuleNodeDebugEvent event = generateEvent(tenantId, entityId, eventType);
event.setId(new EventId(Uuids.timeBased())); event.setId(new EventId(Uuids.timeBased()));
event.setCreatedTime(time); event.setCreatedTime(time);
eventService.saveAsync(event).get(); eventService.saveAsync(event).get();

31
dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.AbstractJpaDaoTest;
import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.event.EventDao;
import org.thingsboard.server.dao.model.sql.RuleNodeDebugEventEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -42,6 +43,10 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
@Autowired @Autowired
private EventDao eventDao; private EventDao eventDao;
@Autowired
private RuleNodeDebugEventRepository ruleNodeDebugEventRepository;
UUID tenantId = Uuids.timeBased(); UUID tenantId = Uuids.timeBased();
@ -106,6 +111,23 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
} }
@Test
public void findLatestDebugRuleNodeInEvent() throws Exception {
UUID entityId = Uuids.timeBased();
RuleNodeDebugEventEntity event = getDebugInEventEntity(Uuids.timeBased(), tenantId, entityId);
eventDao.saveAsync(event.toData()).get(1, TimeUnit.MINUTES);
Thread.sleep(2);
RuleNodeDebugEventEntity event2 = getDebugInEventEntity(Uuids.timeBased(), tenantId, entityId);
eventDao.saveAsync(event2.toData()).get(1, TimeUnit.MINUTES);
RuleNodeDebugEventEntity foundEvent = ruleNodeDebugEventRepository.findLatestDebugRuleNodeInEvent(tenantId, entityId).get();
assertNotNull("Events expected to be not null", foundEvent);
assertEquals(event2.getEventType(), foundEvent.getEventType());
assertEquals(event2.getId(), foundEvent.getId());
}
private Event getStatsEvent(UUID eventId, UUID tenantId, UUID entityId) { private Event getStatsEvent(UUID eventId, UUID tenantId, UUID entityId) {
StatisticsEvent.StatisticsEventBuilder event = StatisticsEvent.builder(); StatisticsEvent.StatisticsEventBuilder event = StatisticsEvent.builder();
event.id(eventId); event.id(eventId);
@ -117,4 +139,13 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
event.errorsOccurred(0); event.errorsOccurred(0);
return event.build(); return event.build();
} }
private RuleNodeDebugEventEntity getDebugInEventEntity(UUID eventId, UUID tenantId, UUID entityId) {
RuleNodeDebugEventEntity event = new RuleNodeDebugEventEntity();
event.setId(eventId);
event.setEventType("IN");
event.setEntityId(entityId);
event.setTenantId(tenantId);
return event;
}
} }

3
pom.xml

@ -72,7 +72,7 @@
<fasterxml-classmate.version>1.3.4</fasterxml-classmate.version> <fasterxml-classmate.version>1.3.4</fasterxml-classmate.version>
<auth0-jwt.version>4.2.1</auth0-jwt.version> <auth0-jwt.version>4.2.1</auth0-jwt.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version> <json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>3.10.0</californium.version> <californium.version>3.11.0</californium.version>
<leshan.version>2.0.0-M14</leshan.version> <leshan.version>2.0.0-M14</leshan.version>
<gson.version>2.9.0</gson.version> <gson.version>2.9.0</gson.version>
<freemarker.version>2.3.30</freemarker.version> <freemarker.version>2.3.30</freemarker.version>
@ -830,7 +830,6 @@
<exclude>src/main/scripts/control/**</exclude> <exclude>src/main/scripts/control/**</exclude>
<exclude>src/main/scripts/windows/**</exclude> <exclude>src/main/scripts/windows/**</exclude>
<exclude>src/main/resources/public/static/rulenode/**</exclude> <exclude>src/main/resources/public/static/rulenode/**</exclude>
<exclude>src/main/java/org/eclipse/leshan/server/observation//**</exclude>
<exclude>**/*.proto.js</exclude> <exclude>**/*.proto.js</exclude>
<exclude>docker/haproxy/**</exclude> <exclude>docker/haproxy/**</exclude>
<exclude>docker/tb-node/**</exclude> <exclude>docker/tb-node/**</exclude>

2
rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java

@ -62,7 +62,7 @@ public class RestJsonConverter {
KvEntry entry = parseValue(key, ts.get(VALUE)); KvEntry entry = parseValue(key, ts.get(VALUE));
return new BasicTsKvEntry(ts.get(TS).asLong(), entry); return new BasicTsKvEntry(ts.get(TS).asLong(), entry);
} }
).collect(Collectors.toList())) ).toList())
); );
return result; return result;
} else { } else {

14
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java

@ -87,24 +87,23 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
Map<String, Object> entityBody = new HashMap<>(); Map<String, Object> entityBody = new HashMap<>();
JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData()); JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData());
switch (actionType) { switch (actionType) {
case ATTRIBUTES_UPDATED: case ATTRIBUTES_UPDATED, POST_ATTRIBUTES -> {
case POST_ATTRIBUTES:
entityBody.put("kv", dataJson); entityBody.put("kv", dataJson);
entityBody.put(SCOPE, getScope(metadata)); entityBody.put(SCOPE, getScope(metadata));
if (EdgeEventActionType.POST_ATTRIBUTES.equals(actionType)) { if (EdgeEventActionType.POST_ATTRIBUTES.equals(actionType)) {
entityBody.put("isPostAttributes", true); entityBody.put("isPostAttributes", true);
} }
break; }
case ATTRIBUTES_DELETED: case ATTRIBUTES_DELETED -> {
List<String> keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() { List<String> keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() {
}); });
entityBody.put("keys", keys); entityBody.put("keys", keys);
entityBody.put(SCOPE, getScope(metadata)); entityBody.put(SCOPE, getScope(metadata));
break; }
case TIMESERIES_UPDATED: case TIMESERIES_UPDATED -> {
entityBody.put("data", dataJson); entityBody.put("data", dataJson);
entityBody.put("ts", msg.getMetaDataTs()); entityBody.put("ts", msg.getMetaDataTs());
break; }
} }
return buildEvent(ctx.getTenantId(), return buildEvent(ctx.getTenantId(),
actionType, actionType,
@ -179,4 +178,5 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
return msg.isTypeOneOf(POST_TELEMETRY_REQUEST, POST_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, TIMESERIES_UPDATED, return msg.isTypeOneOf(POST_TELEMETRY_REQUEST, POST_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, TIMESERIES_UPDATED,
ALARM, CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT, TO_SERVER_RPC_REQUEST); ALARM, CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT, TO_SERVER_RPC_REQUEST);
} }
} }

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java

@ -30,4 +30,5 @@ public class BaseTbMsgPushNodeConfiguration implements NodeConfiguration<BaseTbM
configuration.setScope(AttributeScope.SERVER_SCOPE.name()); configuration.setScope(AttributeScope.SERVER_SCOPE.name());
return configuration; return configuration;
} }
} }

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java

@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.edge;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.AttributeScope;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@ -26,7 +26,8 @@ public class TbMsgPushToCloudNodeConfiguration extends BaseTbMsgPushNodeConfigur
@Override @Override
public TbMsgPushToCloudNodeConfiguration defaultConfiguration() { public TbMsgPushToCloudNodeConfiguration defaultConfiguration() {
TbMsgPushToCloudNodeConfiguration configuration = new TbMsgPushToCloudNodeConfiguration(); TbMsgPushToCloudNodeConfiguration configuration = new TbMsgPushToCloudNodeConfiguration();
configuration.setScope(DataConstants.SERVER_SCOPE); configuration.setScope(AttributeScope.SERVER_SCOPE.name());
return configuration; return configuration;
} }
} }

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java

@ -155,4 +155,5 @@ public class TbMsgPushToEdgeNode extends AbstractTbMsgPushNode<TbMsgPushToEdgeNo
edgeEvent.setEdgeId(edgeId); edgeEvent.setEdgeId(edgeId);
return ctx.getEdgeEventService().saveAsync(edgeEvent); return ctx.getEdgeEventService().saveAsync(edgeEvent);
} }
} }

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java

@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.edge;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.AttributeScope;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@ -26,7 +26,8 @@ public class TbMsgPushToEdgeNodeConfiguration extends BaseTbMsgPushNodeConfigura
@Override @Override
public TbMsgPushToEdgeNodeConfiguration defaultConfiguration() { public TbMsgPushToEdgeNodeConfiguration defaultConfiguration() {
TbMsgPushToEdgeNodeConfiguration configuration = new TbMsgPushToEdgeNodeConfiguration(); TbMsgPushToEdgeNodeConfiguration configuration = new TbMsgPushToEdgeNodeConfiguration();
configuration.setScope(DataConstants.SERVER_SCOPE); configuration.setScope(AttributeScope.SERVER_SCOPE.name());
return configuration; return configuration;
} }
} }

27
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -46,7 +47,9 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode(type = ComponentType.ENRICHMENT, @RuleNode(type = ComponentType.ENRICHMENT,
name = "calculate delta", relationTypes = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE, TbNodeConnectionType.OTHER}, name = "calculate delta",
version = 1,
relationTypes = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE, TbNodeConnectionType.OTHER},
configClazz = CalculateDeltaNodeConfiguration.class, configClazz = CalculateDeltaNodeConfiguration.class,
nodeDescription = "Calculates delta and amount of time passed between previous timeseries key reading " + nodeDescription = "Calculates delta and amount of time passed between previous timeseries key reading " +
"and current value for this key from the incoming message", "and current value for this key from the incoming message",
@ -101,6 +104,11 @@ public class CalculateDeltaNode implements TbNode {
return; return;
} }
if (config.isExcludeZeroDeltas() && delta.doubleValue() == 0) {
ctx.tellSuccess(msg);
return;
}
if (config.getRound() != null) { if (config.getRound() != null) {
delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP); delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP);
} }
@ -128,6 +136,23 @@ public class CalculateDeltaNode implements TbNode {
} }
} }
@Override
public TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
boolean hasChanges = false;
switch (fromVersion) {
case 0:
String excludeZeroDeltas = "excludeZeroDeltas";
if (!oldConfiguration.has(excludeZeroDeltas)) {
hasChanges = true;
((ObjectNode) oldConfiguration).put(excludeZeroDeltas, false);
}
break;
default:
break;
}
return new TbPair<>(hasChanges, oldConfiguration);
}
private ListenableFuture<ValueWithTs> fetchLatestValueAsync(EntityId entityId) { private ListenableFuture<ValueWithTs> fetchLatestValueAsync(EntityId entityId) {
return Futures.transform(timeseriesService.findLatest(ctx.getTenantId(), entityId, Collections.singletonList(config.getInputValueKey())), return Futures.transform(timeseriesService.findLatest(ctx.getTenantId(), entityId, Collections.singletonList(config.getInputValueKey())),
list -> extractValue(list.get(0)) list -> extractValue(list.get(0))

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java

@ -30,6 +30,7 @@ public class CalculateDeltaNodeConfiguration implements NodeConfiguration<Calcul
private String periodValueKey; private String periodValueKey;
private Integer round; private Integer round;
private boolean tellFailureIfDeltaIsNegative; private boolean tellFailureIfDeltaIsNegative;
private boolean excludeZeroDeltas;
@Override @Override
public CalculateDeltaNodeConfiguration defaultConfiguration() { public CalculateDeltaNodeConfiguration defaultConfiguration() {
@ -40,6 +41,7 @@ public class CalculateDeltaNodeConfiguration implements NodeConfiguration<Calcul
configuration.setAddPeriodBetweenMsgs(false); configuration.setAddPeriodBetweenMsgs(false);
configuration.setPeriodValueKey("periodInMs"); configuration.setPeriodValueKey("periodInMs");
configuration.setTellFailureIfDeltaIsNegative(true); configuration.setTellFailureIfDeltaIsNegative(true);
configuration.setExcludeZeroDeltas(false);
return configuration; return configuration;
} }

30
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java

@ -15,11 +15,14 @@
*/ */
package org.thingsboard.rule.engine.mqtt; package org.thingsboard.rule.engine.mqtt;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClient;
import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttClientConfig;
import org.thingsboard.mqtt.MqttConnectResult; import org.thingsboard.mqtt.MqttConnectResult;
@ -35,6 +38,7 @@ import org.thingsboard.rule.engine.external.TbAbstractExternalNode;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode; import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -81,7 +85,8 @@ public class TbMqttNode extends TbAbstractExternalNode {
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg); String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg);
var tbMsg = ackIfNeeded(ctx, msg); var tbMsg = ackIfNeeded(ctx, msg);
this.mqttClient.publish(topic, Unpooled.wrappedBuffer(tbMsg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE, mqttNodeConfiguration.isRetainedMessage()) this.mqttClient.publish(topic, Unpooled.wrappedBuffer(getData(tbMsg, mqttNodeConfiguration.isParseToPlainText()).getBytes(UTF8)),
MqttQoS.AT_LEAST_ONCE, mqttNodeConfiguration.isRetainedMessage())
.addListener(future -> { .addListener(future -> {
if (future.isSuccess()) { if (future.isSuccess()) {
tellSuccess(ctx, tbMsg); tellSuccess(ctx, tbMsg);
@ -153,4 +158,27 @@ public class TbMqttNode extends TbAbstractExternalNode {
return this.mqttNodeConfiguration.isSsl() ? this.mqttNodeConfiguration.getCredentials().initSslContext() : null; return this.mqttNodeConfiguration.isSsl() ? this.mqttNodeConfiguration.getCredentials().initSslContext() : null;
} }
private String getData(TbMsg tbMsg, boolean parseToPlainText) {
if (parseToPlainText) {
return JacksonUtil.toPlainText(tbMsg.getData());
}
return tbMsg.getData();
}
@Override
public TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
boolean hasChanges = false;
switch (fromVersion) {
case 0:
String parseToPlainText = "parseToPlainText";
if (!oldConfiguration.has(parseToPlainText)) {
hasChanges = true;
((ObjectNode) oldConfiguration).put(parseToPlainText, false);
}
break;
default:
break;
}
return new TbPair<>(hasChanges, oldConfiguration);
}
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java

@ -33,6 +33,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf
private boolean cleanSession; private boolean cleanSession;
private boolean ssl; private boolean ssl;
private boolean parseToPlainText;
private ClientCredentials credentials; private ClientCredentials credentials;
@Override @Override
@ -44,6 +45,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration<TbMqttNodeConf
configuration.setCleanSession(true); configuration.setCleanSession(true);
configuration.setSsl(false); configuration.setSsl(false);
configuration.setRetainedMessage(false); configuration.setRetainedMessage(false);
configuration.setParseToPlainText(false);
configuration.setCredentials(new AnonymousCredentials()); configuration.setCredentials(new AnonymousCredentials());
return configuration; return configuration;
} }

14
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -238,23 +238,11 @@ public class TbHttpClient {
private String getData(TbMsg tbMsg, boolean ignoreBody, boolean parseToPlainText) { private String getData(TbMsg tbMsg, boolean ignoreBody, boolean parseToPlainText) {
if (!ignoreBody && parseToPlainText) { if (!ignoreBody && parseToPlainText) {
return parseJsonStringToPlainText(tbMsg.getData()); return JacksonUtil.toPlainText(tbMsg.getData());
} }
return tbMsg.getData(); return tbMsg.getData();
} }
protected String parseJsonStringToPlainText(String data) {
if (data.startsWith("\"") && data.endsWith("\"") && data.length() >= 2) {
final String dataBefore = data;
try {
data = JacksonUtil.fromString(data, String.class);
} catch (Exception ignored) {}
log.trace("Trimming double quotes. Before trim: [{}], after trim: [{}]", dataBefore, data);
}
return data;
}
private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) { private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) {
TbMsgMetaData metaData = origMsg.getMetaData(); TbMsgMetaData metaData = origMsg.getMetaData();
HttpStatus httpStatus = (HttpStatus) response.getStatusCode(); HttpStatus httpStatus = (HttpStatus) response.getStatusCode();

120
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java

@ -16,19 +16,26 @@
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatcher;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest;
import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.TestDbCallbackExecutor;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -48,7 +55,10 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@ -63,10 +73,11 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class CalculateDeltaNodeTest { public class CalculateDeltaNodeTest extends AbstractRuleNodeUpgradeTest {
private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
@ -75,13 +86,13 @@ public class CalculateDeltaNodeTest {
private TbContext ctxMock; private TbContext ctxMock;
@Mock @Mock
private TimeseriesService timeseriesServiceMock; private TimeseriesService timeseriesServiceMock;
@Spy
private CalculateDeltaNode node; private CalculateDeltaNode node;
private CalculateDeltaNodeConfiguration config; private CalculateDeltaNodeConfiguration config;
private TbNodeConfiguration nodeConfiguration; private TbNodeConfiguration nodeConfiguration;
@BeforeEach @BeforeEach
public void setUp() throws TbNodeException { public void setUp() throws TbNodeException {
node = new CalculateDeltaNode();
config = new CalculateDeltaNodeConfiguration().defaultConfiguration(); config = new CalculateDeltaNodeConfiguration().defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
@ -424,6 +435,91 @@ public class CalculateDeltaNodeTest {
.hasMessage("Calculation failed. JSON values are not supported!"); .hasMessage("Calculation failed. JSON values are not supported!");
} }
@ParameterizedTest
@MethodSource("CalculateDeltaTestConfig")
public void givenCalculateDeltaConfig_whenOnMsg_thenVerify(CalculateDeltaTestConfig testConfig) throws TbNodeException {
// GIVEN
config.setTellFailureIfDeltaIsNegative(testConfig.isTellFailureIfDeltaIsNegative());
config.setExcludeZeroDeltas(testConfig.isExcludeZeroDeltas());
config.setInputValueKey("temperature");
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(1L, new DoubleDataEntry("temperature", testConfig.getPrevValue())));
var msgData = "{\"temperature\":" + testConfig.getCurrentValue() + ",\"airPressure\":123}";
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
testConfig.getVerificationMethod().accept(ctxMock, msg);
}
private static Stream<CalculateDeltaTestConfig> CalculateDeltaTestConfig() {
return Stream.of(
// delta = 0, tell failure if delta is negative is set to true and exclude zero deltas is set to true so delta should filter out the message.
new CalculateDeltaTestConfig(true, true, 40, 40, (ctx, msg) -> {
verify(ctx).tellSuccess(eq(msg));
verify(ctx).getDbCallbackExecutor();
verifyNoMoreInteractions(ctx);
}),
// delta < 0, tell failure if delta is negative is set to true so it should throw exception.
new CalculateDeltaTestConfig(true, true, 41, 40, (ctx, msg) -> {
var errorCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(ctx).tellFailure(eq(msg), errorCaptor.capture());
verify(ctx).getDbCallbackExecutor();
verifyNoMoreInteractions(ctx);
assertThat(errorCaptor.getValue()).isInstanceOf(IllegalArgumentException.class).hasMessage("Delta value is negative!");
}),
// delta < 0, exclude zero deltas is set to true so it should return message with delta if delta is negative is set to false.
new CalculateDeltaTestConfig(false, true, 41, 40, (ctx, msg) -> {
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).tellSuccess(actualMsgCaptor.capture());
verify(ctx).getDbCallbackExecutor();
verifyNoMoreInteractions(ctx);
String expectedMsgData = "{\"temperature\":40.0,\"airPressure\":123,\"delta\":-1}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}),
// delta = 0, tell failure if delta is negative is set to false and exclude zero deltas is set to true so delta should filter out the message.
new CalculateDeltaTestConfig(false, true, 40, 40, (ctx, msg) -> {
verify(ctx).tellSuccess(eq(msg));
verify(ctx).getDbCallbackExecutor();
verifyNoMoreInteractions(ctx);
}),
// delta > 0, exclude zero deltas is set to true so it should return message with delta.
new CalculateDeltaTestConfig(false, true, 39, 40, (ctx, msg) -> {
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).tellSuccess(actualMsgCaptor.capture());
verify(ctx).getDbCallbackExecutor();
verifyNoMoreInteractions(ctx);
String expectedMsgData = "{\"temperature\":40.0,\"airPressure\":123,\"delta\":1}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}),
// delta > 0, exclude zero deltas is set to false so it should return message with delta.
new CalculateDeltaTestConfig(false, false, 39, 40, (ctx, msg) -> {
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).tellSuccess(actualMsgCaptor.capture());
verify(ctx).getDbCallbackExecutor();
verifyNoMoreInteractions(ctx);
String expectedMsgData = "{\"temperature\":40.0,\"airPressure\":123,\"delta\":1}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
})
);
}
@Data
@RequiredArgsConstructor
private static class CalculateDeltaTestConfig {
private final boolean tellFailureIfDeltaIsNegative;
private final boolean excludeZeroDeltas;
private final double prevValue;
private final double currentValue;
private final BiConsumer<TbContext, TbMsg> verificationMethod;
}
private void mockFindLatest(TsKvEntry tsKvEntry) { private void mockFindLatest(TsKvEntry tsKvEntry) {
when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(timeseriesServiceMock.findLatestSync( when(timeseriesServiceMock.findLatestSync(
@ -457,4 +553,24 @@ public class CalculateDeltaNodeTest {
} }
private static Stream<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() {
return Stream.of(
// default config for version 0
Arguments.of(0,
"{\"inputValueKey\":\"pulseCounter\",\"outputValueKey\":\"delta\",\"useCache\":true,\"addPeriodBetweenMsgs\":false, \"periodValueKey\":\"periodInMs\", \"round\":null,\"tellFailureIfDeltaIsNegative\":true}",
true,
"{\"inputValueKey\":\"pulseCounter\",\"outputValueKey\":\"delta\",\"useCache\":true,\"addPeriodBetweenMsgs\":false, \"periodValueKey\":\"periodInMs\", \"round\":null,\"tellFailureIfDeltaIsNegative\":true, \"excludeZeroDeltas\":false}"),
// default config for version 1 with upgrade from version 0
Arguments.of(1,
"{\"inputValueKey\":\"pulseCounter\",\"outputValueKey\":\"delta\",\"useCache\":true,\"addPeriodBetweenMsgs\":false, \"periodValueKey\":\"periodInMs\", \"round\":null,\"tellFailureIfDeltaIsNegative\":true, \"excludeZeroDeltas\":false}",
false,
"{\"inputValueKey\":\"pulseCounter\",\"outputValueKey\":\"delta\",\"useCache\":true,\"addPeriodBetweenMsgs\":false, \"periodValueKey\":\"periodInMs\", \"round\":null,\"tellFailureIfDeltaIsNegative\":true, \"excludeZeroDeltas\":false}")
);
}
@Override
protected TbNode getTestNode() {
return node;
}
} }

60
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.mqtt;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.provider.Arguments;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest;
import org.thingsboard.rule.engine.api.TbNode;
import java.util.stream.Stream;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
class TbMqttNodeTest extends AbstractRuleNodeUpgradeTest {
@Spy
TbMqttNode node;
@BeforeEach
public void setUp() throws Exception {
node = mock(TbMqttNode.class);
}
private static Stream<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() {
return Stream.of(
// default config for version 0
Arguments.of(0,
"{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"}}",
true,
"{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false}"),
// default config for version 1 with upgrade from version 0
Arguments.of(1,
"{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false}",
false,
"{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false}")
);
}
@Override
protected TbNode getTestNode() {
return node;
}
}

16
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java

@ -22,13 +22,10 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockserver.integration.ClientAndServer; import org.mockserver.integration.ClientAndServer;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -47,7 +44,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -222,16 +218,4 @@ public class TbHttpClientTest {
Assertions.assertEquals(data.get("Set-Cookie"), "[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]"); Assertions.assertEquals(data.get("Set-Cookie"), "[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]");
} }
@ParameterizedTest
@ValueSource(strings = { "false", "\"", "\"\"", "\"This is a string with double quotes\"", "Path: /home/developer/test.txt",
"First line\nSecond line\n\nFourth line", "Before\rAfter", "Tab\tSeparated\tValues", "Test\bbackspace", "[]",
"[1, 2, 3]", "{\"key\": \"value\"}", "{\n\"temperature\": 25.5,\n\"humidity\": 50.2\n\"}", "Expression: (a + b) * c",
"世界", "Україна", "\u1F1FA\u1F1E6", "🇺🇦"})
public void testParseJsonStringToPlainText(String original) {
Mockito.when(client.parseJsonStringToPlainText(anyString())).thenCallRealMethod();
String serialized = JacksonUtil.toString(original);
Assertions.assertNotNull(serialized);
Assertions.assertEquals(original, client.parseJsonStringToPlainText(serialized));
}
} }

296
transport/lwm2m/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java

@ -1,296 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
* Michał Wadowski (Orange) - Add Observe-Composite feature.
*/
package org.eclipse.leshan.server.observation;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.SingleObservation;
import org.eclipse.leshan.core.peer.LwM2mPeer;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint;
import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider;
import org.eclipse.leshan.server.profile.ClientProfile;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore;
import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Implementation of the {@link ObservationService} accessing the persisted observation via the provided
* {@link RegistrationStore}.
*
* When a new observation is added or changed or canceled, the registered listeners are notified.
*/
@Slf4j
public class ObservationServiceImpl implements ObservationService, LwM2mNotificationReceiver {
private final Logger LOG = LoggerFactory.getLogger(ObservationServiceImpl.class);
private final RegistrationStore registrationStore;
private final LwM2mServerEndpointsProvider endpointProvider;
private final boolean updateRegistrationOnNotification;
private final List<ObservationListener> listeners = new CopyOnWriteArrayList<>();;
/**
* Creates an instance of {@link ObservationServiceImpl}
*/
public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider) {
this(store, endpointProvider, false);
}
/**
* Creates an instance of {@link ObservationServiceImpl}
*
* @param updateRegistrationOnNotification will activate registration update on observe notification.
*
* @since 1.1
*/
public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider,
boolean updateRegistrationOnNotification) {
this.registrationStore = store;
this.updateRegistrationOnNotification = updateRegistrationOnNotification;
this.endpointProvider = endpointProvider;
}
@Override
public int cancelObservations(Registration registration) {
// check registration id
String registrationId = registration.getId();
if (registrationId == null)
return 0;
Collection<Observation> observations = registrationStore.removeObservations(registrationId);
if (observations == null)
return 0;
for (Observation observation : observations) {
cancel(observation);
}
return observations.size();
}
@Override
public int cancelObservations(Registration registration, String nodePath) {
if (registration == null || registration.getId() == null || nodePath == null || nodePath.isEmpty())
return 0;
Set<Observation> observations = getObservationsForCancel(registration.getId(), nodePath);
for (Observation observation : observations) {
cancelObservation(observation);
}
return observations.size();
}
@Override
public int cancelCompositeObservations(Registration registration, String[] nodePaths) {
if (registration == null || registration.getId() == null || nodePaths == null || nodePaths.length == 0)
return 0;
Set<Observation> observations = getCompositeObservationsForCancel(registration.getId(), nodePaths);
for (Observation observation : observations) {
cancelObservation(observation);
}
return observations.size();
}
@Override
public void cancelObservation(Observation observation) {
if (observation == null)
return;
registrationStore.removeObservation(observation.getRegistrationId(), observation.getId());
cancel(observation);
}
private void cancel(Observation observation) {
List<LwM2mServerEndpoint> endpoints = endpointProvider.getEndpoints();
for (LwM2mServerEndpoint lwM2mEndpoint : endpoints) {
lwM2mEndpoint.cancelObservation(observation);
}
for (ObservationListener listener : listeners) {
listener.cancelled(observation);
}
}
@Override
public Set<Observation> getObservations(Registration registration) {
return getObservations(registration.getId());
}
private Set<Observation> getObservations(String registrationId) {
if (registrationId == null)
return Collections.emptySet();
return new HashSet<>(registrationStore.getObservations(registrationId));
}
private Set<Observation> getCompositeObservationsForCancel(String registrationId, String[] nodePaths) {
if (registrationId == null || nodePaths == null)
return Collections.emptySet();
// array of String to array of LWM2M path
List<LwM2mPath> lwPaths = new ArrayList<>(nodePaths.length);
for (int i = 0; i < nodePaths.length; i++) {
lwPaths.add(new LwM2mPath(nodePaths[i]));
}
// search composite-observation
Set<Observation> result = new HashSet<>();
for (Observation obs : getObservations(registrationId)) {
if (obs instanceof CompositeObservation) {
if (lwPaths.equals(((CompositeObservation) obs).getPaths())) {
result.add(obs);
}
}
}
return result;
}
private Set<Observation> getObservationsForCancel(String registrationId, String nodePath) {
if (registrationId == null || nodePath == null)
return Collections.emptySet();
Set<Observation> result = new HashSet<>();
LwM2mPath lwPath = new LwM2mPath(nodePath);
for (Observation obs : getObservations(registrationId)) {
if (obs instanceof SingleObservation) {
LwM2mPath lwPathObs = ((SingleObservation) obs).getPath();
if (lwPath.equals(lwPathObs) || lwPathObs.startWith(lwPath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs
result.add(obs);
} else if (!lwPath.equals(lwPathObs) && lwPath.startWith(lwPathObs)) { // nodePath = "3/0/9", lwPathObs = "3": error...
String errorMsg = String.format(
"Unexpected error <cancelObservation>: There is registration with id [%s] existing observation [%s] includes input observation [%s]!",
registrationId, lwPathObs, lwPath);
throw new IllegalStateException(errorMsg);
}
}
}
return result;
}
@Override
public void addListener(ObservationListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(ObservationListener listener) {
listeners.remove(listener);
}
private Registration updateRegistrationOnRegistration(Observation observation, LwM2mPeer sender,
ClientProfile profile) {
if (updateRegistrationOnNotification) {
RegistrationUpdate regUpdate = new RegistrationUpdate(observation.getRegistrationId(), sender, null, null,
null, null, null, null, null, null, null, null);
UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate);
if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) {
String errorMsg = String.format(
"Unexpected error: There is no registration with id %s for this observation %s",
observation.getRegistrationId(), observation);
LOG.error(errorMsg);
throw new IllegalStateException(errorMsg);
}
return updatedRegistration.getUpdatedRegistration();
}
return profile.getRegistration();
}
// ********** NotificationListener interface **********//
@Override
public void onNotification(SingleObservation observation, LwM2mPeer sender, ClientProfile profile,
ObserveResponse response) {
try {
Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile);
for (ObservationListener listener : listeners) {
listener.onResponse(observation, updatedRegistration, response);
}
} catch (Exception e) {
for (ObservationListener listener : listeners) {
listener.onError(observation, profile.getRegistration(), e);
}
}
}
@Override
public void onNotification(CompositeObservation observation, LwM2mPeer sender, ClientProfile profile,
ObserveCompositeResponse response) {
try {
Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile);
for (ObservationListener listener : listeners) {
listener.onResponse(observation, updatedRegistration, response);
}
} catch (Exception e) {
for (ObservationListener listener : listeners) {
listener.onError(observation, profile.getRegistration(), e);
}
}
}
@Override
public void onError(Observation observation, LwM2mPeer sender, ClientProfile profile, Exception error) {
for (ObservationListener listener : listeners) {
listener.onError(observation, profile.getRegistration(), error);
}
}
@Override
public void newObservation(Observation observation, Registration registration) {
for (ObservationListener listener : listeners) {
listener.newObservation(observation, registration);
}
}
@Override
public void cancelled(Observation observation) {
for (ObservationListener listener : listeners) {
listener.cancelled(observation);
}
}
}
Loading…
Cancel
Save