Browse Source

Merge branch 'master' into feature/docker-improve

pull/145/head
Volodymyr Babak 9 years ago
parent
commit
a9426676b7
  1. 1
      .gitignore
  2. 3
      application/.gitignore
  3. 2
      application/pom.xml
  4. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 28
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  6. 199
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  7. 17
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  8. 2
      application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java
  9. 2
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  10. 2
      application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
  11. 4
      application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
  12. 234
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  13. 104
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  14. 32
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  15. 29
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  16. 194
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  17. 24
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  18. 6
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java
  19. 15
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java
  20. 4
      application/src/main/resources/thingsboard.yml
  21. 4
      application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java
  22. 2
      common/data/pom.xml
  23. 18
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  24. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  25. 40
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  26. 45
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmId.java
  27. 32
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
  28. 25
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSeverity.java
  29. 25
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java
  30. 166
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  31. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java
  32. 16
      common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java
  33. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java
  34. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdDeserializer.java
  35. 56
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  36. 37
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdSerializer.java
  37. 21
      common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java
  38. 103
      common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
  39. 2
      common/message/pom.xml
  40. 2
      common/pom.xml
  41. 2
      common/transport/pom.xml
  42. 2
      dao/pom.xml
  43. 22
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  44. 41
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  45. 90
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  46. 104
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java
  47. 50
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetSearchQuery.java
  48. 59
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  49. 32
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java
  50. 292
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  51. 17
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
  52. 22
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  53. 4
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  54. 14
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  55. 46
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceSearchQuery.java
  56. 3
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  57. 46
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  58. 37
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  59. 235
      dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java
  60. 23
      dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java
  61. 20
      dao/src/main/java/org/thingsboard/server/dao/model/EventEntity.java
  62. 27
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  63. 18
      dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
  64. 3
      dao/src/main/java/org/thingsboard/server/dao/plugin/PluginService.java
  65. 279
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
  66. 257
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  67. 31
      dao/src/main/java/org/thingsboard/server/dao/relation/EntityRelationsQuery.java
  68. 25
      dao/src/main/java/org/thingsboard/server/dao/relation/EntitySearchDirection.java
  69. 35
      dao/src/main/java/org/thingsboard/server/dao/relation/EntityTypeFilter.java
  70. 47
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  71. 49
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  72. 48
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationsSearchParameters.java
  73. 15
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
  74. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleService.java
  75. 14
      dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
  76. 15
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  77. 34
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  78. 77
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesDao.java
  79. 44
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  80. 13
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
  81. 11
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  82. 6
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  83. 51
      dao/src/main/resources/schema.cql
  84. 4
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  85. 283
      dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java
  86. 26
      dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java
  87. 4
      docker/docker-compose.yml
  88. 4
      docker/thingsboard-db-schema/build_and_deploy.sh
  89. 4
      docker/thingsboard/build_and_deploy.sh
  90. 2
      extensions-api/pom.xml
  91. 5
      extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/UnauthorizedException.java
  92. 26
      extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java
  93. 7
      extensions-core/pom.xml
  94. 78
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/SubscriptionManager.java
  95. 3
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java
  96. 5
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/SubscriptionCmd.java
  97. 29
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryFeature.java
  98. 287
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java
  99. 110
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRpcMsgHandler.java
  100. 58
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java

1
.gitignore

@ -1,3 +1,4 @@
*.toDelete
output/** output/**
*.class *.class
*~ *~

3
application/.gitignore

@ -1 +1,2 @@
!bin/ !bin/
/bin/

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

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

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
@ -80,6 +81,9 @@ public class ActorSystemContext {
@Autowired @Autowired
@Getter private DeviceService deviceService; @Getter private DeviceService deviceService;
@Autowired
@Getter private AssetService assetService;
@Autowired @Autowired
@Getter private TenantService tenantService; @Getter private TenantService tenantService;

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

@ -51,13 +51,7 @@ import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import java.util.HashMap; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -205,25 +199,21 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
refreshAttributes(msg); refreshAttributes(msg);
Set<AttributeKey> keys = msg.getDeletedKeys();
if (attributeSubscriptions.size() > 0) { if (attributeSubscriptions.size() > 0) {
ToDeviceMsg notification = null; ToDeviceMsg notification = null;
if (msg.isDeleted()) { if (msg.isDeleted()) {
List<AttributeKey> sharedKeys = keys.stream() List<AttributeKey> sharedKeys = msg.getDeletedKeys().stream()
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.collect(Collectors.toList()); .collect(Collectors.toList());
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys)); notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys));
} else { } else {
List<AttributeKvEntry> attributes = keys.stream() if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
.map(key -> deviceAttributes.getServerPublicAttribute(key.getAttributeKey())) if (attributes.size() > 0) {
.filter(Optional::isPresent) notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
.map(Optional::get) } else {
.collect(Collectors.toList()); logger.debug("[{}] No public server side attributes changed!", deviceId);
if (attributes.size() > 0) { }
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
} else {
logger.debug("[{}] No public server side attributes changed!", deviceId);
} }
} }
if (notification != null) { if (notification != null) {

199
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java

@ -17,7 +17,6 @@ package org.thingsboard.server.actors.plugin;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -30,15 +29,19 @@ 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 lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Customer;
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.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.*;
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.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
@ -53,7 +56,6 @@ import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRe
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import akka.actor.ActorRef; import akka.actor.ActorRef;
import org.w3c.dom.Attr;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -91,103 +93,107 @@ public final class PluginProcessingContext implements PluginContext {
} }
@Override @Override
public void saveAttributes(final TenantId tenantId, final DeviceId deviceId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) { public void saveAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(entityId, scope, attributes);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> { Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
onDeviceAttributesChanged(tenantId, deviceId, scope, attributes); if (entityId.getEntityType() == EntityType.DEVICE) {
onDeviceAttributesChanged(tenantId, new DeviceId(entityId.getId()), scope, attributes);
}
return null; return null;
}), executor); }), executor);
})); }));
} }
@Override @Override
public void removeAttributes(final TenantId tenantId, final DeviceId deviceId, final String scope, final List<String> keys, final PluginCallback<Void> callback) { public void removeAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<String> keys, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys); ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(entityId, scope, keys);
Futures.addCallback(future, getCallback(callback, v -> null), executor); Futures.addCallback(future, getCallback(callback, v -> null), executor);
onDeviceAttributesDeleted(tenantId, deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet())); if (entityId.getEntityType() == EntityType.DEVICE) {
onDeviceAttributesDeleted(tenantId, new DeviceId(entityId.getId()), keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
}
})); }));
} }
@Override @Override
public void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) { public void loadAttribute(EntityId entityId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey); ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKey);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
})); }));
} }
@Override @Override
public void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(EntityId entityId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys); ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKeys);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
})); }));
} }
@Override @Override
public void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(EntityId entityId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(deviceId, attributeType); ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(entityId, attributeType);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
})); }));
} }
@Override @Override
public void loadAttributes(final DeviceId deviceId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(deviceId, attributeType))); attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(entityId, attributeType)));
convertFuturesAndAddCallback(callback, futures); convertFuturesAndAddCallback(callback, futures);
})); }));
} }
@Override @Override
public void loadAttributes(final DeviceId deviceId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys))); attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(entityId, attributeType, attributeKeys)));
convertFuturesAndAddCallback(callback, futures); convertFuturesAndAddCallback(callback, futures);
})); }));
} }
@Override @Override
public void saveTsData(final DeviceId deviceId, final TsKvEntry entry, final PluginCallback<Void> callback) { public void saveTsData(final EntityId entityId, final TsKvEntry entry, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(entityId, entry);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
})); }));
} }
@Override @Override
public void saveTsData(final DeviceId deviceId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) { public void saveTsData(final EntityId entityId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(entityId, entries);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
})); }));
} }
@Override @Override
public void loadTimeseries(final DeviceId deviceId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) { public void loadTimeseries(final EntityId entityId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, queries); ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(entityId, queries);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
})); }));
} }
@Override @Override
public void loadLatestTimeseries(final DeviceId deviceId, final PluginCallback<List<TsKvEntry>> callback) { public void loadLatestTimeseries(final EntityId entityId, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId); ResultSetFuture future = pluginCtx.tsService.findAllLatest(entityId);
Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor); Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor);
})); }));
} }
@Override @Override
public void loadLatestTimeseries(final DeviceId deviceId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) { public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> { validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys);
Futures.addCallback(rsListFuture, getListCallback(callback, rsList -> Futures.addCallback(rsListFuture, getListCallback(callback, rsList ->
{ {
List<TsKvEntry> result = new ArrayList<>(); List<TsKvEntry> result = new ArrayList<>();
@ -270,24 +276,101 @@ public final class PluginProcessingContext implements PluginContext {
validate(deviceId, new ValidationCallback(callback, ctx -> callback.onSuccess(ctx, null))); validate(deviceId, new ValidationCallback(callback, ctx -> callback.onSuccess(ctx, null)));
} }
private void validate(DeviceId deviceId, ValidationCallback callback) { private void validate(EntityId entityId, ValidationCallback callback) {
if (securityCtx.isPresent()) { if (securityCtx.isPresent()) {
final PluginApiCallSecurityContext ctx = securityCtx.get(); final PluginApiCallSecurityContext ctx = securityCtx.get();
if (ctx.isTenantAdmin() || ctx.isCustomerUser()) { if (ctx.isTenantAdmin() || ctx.isCustomerUser() || ctx.isSystemAdmin()) {
ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(deviceId); switch (entityId.getEntityType()) {
Futures.addCallback(deviceFuture, getCallback(callback, device -> { case DEVICE:
if (device == null) { if (ctx.isSystemAdmin()) {
return Boolean.FALSE; callback.onSuccess(this, Boolean.FALSE);
} else {
if (!device.getTenantId().equals(ctx.getTenantId())) {
return Boolean.FALSE;
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
return Boolean.FALSE;
} else { } else {
return Boolean.TRUE; ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
Futures.addCallback(deviceFuture, getCallback(callback, device -> {
if (device == null) {
return Boolean.FALSE;
} else {
if (!device.getTenantId().equals(ctx.getTenantId())) {
return Boolean.FALSE;
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
return Boolean.FALSE;
} else {
return Boolean.TRUE;
}
}
}));
} }
} return;
})); case ASSET:
if (ctx.isSystemAdmin()) {
callback.onSuccess(this, Boolean.FALSE);
} else {
ListenableFuture<Asset> assetFuture = pluginCtx.assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
Futures.addCallback(assetFuture, getCallback(callback, asset -> {
if (asset == null) {
return Boolean.FALSE;
} else {
if (!asset.getTenantId().equals(ctx.getTenantId())) {
return Boolean.FALSE;
} else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) {
return Boolean.FALSE;
} else {
return Boolean.TRUE;
}
}
}));
}
return;
case RULE:
if (ctx.isCustomerUser()) {
callback.onSuccess(this, Boolean.FALSE);
} else {
ListenableFuture<RuleMetaData> ruleFuture = pluginCtx.ruleService.findRuleByIdAsync(new RuleId(entityId.getId()));
Futures.addCallback(ruleFuture, getCallback(callback, rule -> rule != null && rule.getTenantId().equals(ctx.getTenantId())));
}
return;
case PLUGIN:
if (ctx.isCustomerUser()) {
callback.onSuccess(this, Boolean.FALSE);
} else {
ListenableFuture<PluginMetaData> pluginFuture = pluginCtx.pluginService.findPluginByIdAsync(new PluginId(entityId.getId()));
Futures.addCallback(pluginFuture, getCallback(callback, plugin -> plugin != null && plugin.getTenantId().equals(ctx.getTenantId())));
}
return;
case CUSTOMER:
if (ctx.isSystemAdmin()) {
callback.onSuccess(this, Boolean.FALSE);
} else {
ListenableFuture<Customer> customerFuture = pluginCtx.customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
Futures.addCallback(customerFuture, getCallback(callback, customer -> {
if (customer == null) {
return Boolean.FALSE;
} else {
if (!customer.getTenantId().equals(ctx.getTenantId())) {
return Boolean.FALSE;
} else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) {
return Boolean.FALSE;
} else {
return Boolean.TRUE;
}
}
}));
}
return;
case TENANT:
if (ctx.isCustomerUser()) {
callback.onSuccess(this, Boolean.FALSE);
} else if (ctx.isSystemAdmin()) {
callback.onSuccess(this, Boolean.TRUE);
} else {
ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> tenant != null && tenant.getId().equals(ctx.getTenantId())));
}
return;
default:
//TODO: add support of other entities
throw new IllegalStateException("Not Implemented!");
}
} else { } else {
callback.onSuccess(this, Boolean.FALSE); callback.onSuccess(this, Boolean.FALSE);
} }
@ -297,8 +380,8 @@ public final class PluginProcessingContext implements PluginContext {
} }
@Override @Override
public Optional<ServerAddress> resolve(DeviceId deviceId) { public Optional<ServerAddress> resolve(EntityId entityId) {
return pluginCtx.routingService.resolve(deviceId); return pluginCtx.routingService.resolveById(entityId);
} }
@Override @Override

17
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java

@ -25,8 +25,13 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
@ -46,7 +51,12 @@ public final class SharedPluginProcessingContext {
final ActorRef currentActor; final ActorRef currentActor;
final ActorSystemContext systemContext; final ActorSystemContext systemContext;
final PluginWebSocketMsgEndpoint msgEndpoint; final PluginWebSocketMsgEndpoint msgEndpoint;
final AssetService assetService;
final DeviceService deviceService; final DeviceService deviceService;
final RuleService ruleService;
final PluginService pluginService;
final CustomerService customerService;
final TenantService tenantService;
final TimeseriesService tsService; final TimeseriesService tsService;
final AttributesService attributesService; final AttributesService attributesService;
final ClusterRpcService rpcService; final ClusterRpcService rpcService;
@ -65,9 +75,14 @@ public final class SharedPluginProcessingContext {
this.msgEndpoint = sysContext.getWsMsgEndpoint(); this.msgEndpoint = sysContext.getWsMsgEndpoint();
this.tsService = sysContext.getTsService(); this.tsService = sysContext.getTsService();
this.attributesService = sysContext.getAttributesService(); this.attributesService = sysContext.getAttributesService();
this.assetService = sysContext.getAssetService();
this.deviceService = sysContext.getDeviceService(); this.deviceService = sysContext.getDeviceService();
this.rpcService = sysContext.getRpcService(); this.rpcService = sysContext.getRpcService();
this.routingService = sysContext.getRoutingService(); this.routingService = sysContext.getRoutingService();
this.ruleService = sysContext.getRuleService();
this.pluginService = sysContext.getPluginService();
this.customerService = sysContext.getCustomerService();
this.tenantService = sysContext.getTenantService();
} }
public PluginId getPluginId() { public PluginId getPluginId() {
@ -89,7 +104,7 @@ public final class SharedPluginProcessingContext {
} }
private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) { private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
Optional<ServerAddress> instance = routingService.resolve(deviceId); Optional<ServerAddress> instance = routingService.resolveById(deviceId);
if (instance.isPresent()) { if (instance.isPresent()) {
log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg); log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg);
rpcFunction.accept(instance.get(), msg); rpcFunction.accept(instance.get(), msg);

2
application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java

@ -38,7 +38,7 @@ public class ValidationCallback implements PluginCallback<Boolean> {
if (value) { if (value) {
action.accept(ctx); action.accept(ctx);
} else { } else {
onFailure(ctx, new UnauthorizedException()); onFailure(ctx, new UnauthorizedException("Permission denied."));
} }
} }

2
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -230,7 +230,7 @@ public class DefaultActorService implements ActorService {
@Override @Override
public void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId) { public void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId) {
DeviceCredentialsUpdateNotificationMsg msg = new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceId); DeviceCredentialsUpdateNotificationMsg msg = new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceId);
Optional<ServerAddress> address = actorContext.getRoutingService().resolve(deviceId); Optional<ServerAddress> address = actorContext.getRoutingService().resolveById(deviceId);
if (address.isPresent()) { if (address.isPresent()) {
rpcService.tell(address.get(), msg); rpcService.tell(address.get(), msg);
} else { } else {

2
application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java

@ -116,7 +116,7 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
@Override @Override
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { public void processClusterEvent(ActorContext context, ClusterEventMsg msg) {
if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) { if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) {
Optional<ServerAddress> newTargetServer = systemContext.getRoutingService().resolve(getDeviceId()); Optional<ServerAddress> newTargetServer = systemContext.getRoutingService().resolveById(getDeviceId());
if (!newTargetServer.equals(currentTargetServer)) { if (!newTargetServer.equals(currentTargetServer)) {
firstMsg = true; firstMsg = true;
currentTargetServer = newTargetServer; currentTargetServer = newTargetServer;

4
application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java

@ -81,13 +81,13 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
} }
protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) { protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) {
Optional<ServerAddress> address = systemContext.getRoutingService().resolve(toForward.getDeviceId()); Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
forwardToAppActor(ctx, toForward, address); forwardToAppActor(ctx, toForward, address);
return address; return address;
} }
protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) { protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolve(toForward.getDeviceId()); Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
if (!newAddress.equals(oldAddress)) { if (!newAddress.equals(oldAddress)) {
if (newAddress.isPresent()) { if (newAddress.isPresent()) {
systemContext.getRpcService().tell(newAddress.get(), systemContext.getRpcService().tell(newAddress.get(),

234
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -0,0 +1,234 @@
/**
* Copyright © 2016-2017 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.controller;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.asset.AssetSearchQuery;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class AssetController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET)
@ResponseBody
public Asset getAssetById(@PathVariable("assetId") String strAssetId) throws ThingsboardException {
checkParameter("assetId", strAssetId);
try {
AssetId assetId = new AssetId(toUUID(strAssetId));
return checkAssetId(assetId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/asset", method = RequestMethod.POST)
@ResponseBody
public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException {
try {
asset.setTenantId(getCurrentUser().getTenantId());
return checkNotNull(assetService.saveAsset(asset));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteAsset(@PathVariable("assetId") String strAssetId) throws ThingsboardException {
checkParameter("assetId", strAssetId);
try {
AssetId assetId = new AssetId(toUUID(strAssetId));
checkAssetId(assetId);
assetService.deleteAsset(assetId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
public Asset assignAssetToCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable("assetId") String strAssetId) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
checkParameter("assetId", strAssetId);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
AssetId assetId = new AssetId(toUUID(strAssetId));
checkAssetId(assetId);
return checkNotNull(assetService.assignAssetToCustomer(assetId, customerId));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody
public Asset unassignAssetFromCustomer(@PathVariable("assetId") String strAssetId) throws ThingsboardException {
checkParameter("assetId", strAssetId);
try {
AssetId assetId = new AssetId(toUUID(strAssetId));
Asset asset = checkAssetId(assetId);
if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Asset isn't assigned to any customer!");
}
return checkNotNull(assetService.unassignAssetFromCustomer(assetId));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
public Asset assignAssetToPublicCustomer(@PathVariable("assetId") String strAssetId) throws ThingsboardException {
checkParameter("assetId", strAssetId);
try {
AssetId assetId = new AssetId(toUUID(strAssetId));
Asset asset = checkAssetId(assetId);
Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId());
return checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<Asset> getTenantAssets(
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
@ResponseBody
public Asset getTenantAsset(
@RequestParam String assetName) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
return checkNotNull(assetService.findAssetByTenantIdAndName(tenantId, assetName));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assets", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<Asset> getCustomerAssets(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
@ResponseBody
public List<Asset> getAssetsByIds(
@RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException {
checkArrayParameter("assetIds", strAssetIds);
try {
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
CustomerId customerId = user.getCustomerId();
List<AssetId> assetIds = new ArrayList<>();
for (String strAssetId : strAssetIds) {
assetIds.add(new AssetId(toUUID(strAssetId)));
}
ListenableFuture<List<Asset>> assets;
if (customerId == null || customerId.isNullUid()) {
assets = assetService.findAssetsByTenantIdAndIdsAsync(tenantId, assetIds);
} else {
assets = assetService.findAssetsByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, assetIds);
}
return checkNotNull(assets.get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", method = RequestMethod.POST)
@ResponseBody
public List<Asset> findByQuery(@RequestBody AssetSearchQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkNotNull(query.getAssetTypes());
checkEntityId(query.getParameters().getEntityId());
try {
List<Asset> assets = checkNotNull(assetService.findAssetsByQuery(query).get());
assets = assets.stream().filter(asset -> {
try {
checkAsset(asset);
return true;
} catch (ThingsboardException e) {
return false;
}
}).collect(Collectors.toList());
return assets;
} catch (Exception e) {
throw handleException(e);
}
}
}

104
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -24,10 +24,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
@ -38,6 +36,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
@ -46,6 +45,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
@ -80,6 +80,9 @@ public abstract class BaseController {
@Autowired @Autowired
protected DeviceService deviceService; protected DeviceService deviceService;
@Autowired
protected AssetService assetService;
@Autowired @Autowired
protected DeviceCredentialsService deviceCredentialsService; protected DeviceCredentialsService deviceCredentialsService;
@ -104,6 +107,9 @@ public abstract class BaseController {
@Autowired @Autowired
protected ActorService actorService; protected ActorService actorService;
@Autowired
protected RelationService relationService;
@ExceptionHandler(ThingsboardException.class) @ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
@ -253,6 +259,43 @@ public abstract class BaseController {
} }
} }
protected void checkEntityId(EntityId entityId) throws ThingsboardException {
try {
checkNotNull(entityId);
validateId(entityId.getId(), "Incorrect entityId " + entityId);
switch (entityId.getEntityType()) {
case DEVICE:
checkDevice(deviceService.findDeviceById(new DeviceId(entityId.getId())));
return;
case CUSTOMER:
checkCustomerId(new CustomerId(entityId.getId()));
return;
case TENANT:
checkTenantId(new TenantId(entityId.getId()));
return;
case PLUGIN:
checkPlugin(new PluginId(entityId.getId()));
return;
case RULE:
checkRule(new RuleId(entityId.getId()));
return;
case ASSET:
checkAsset(assetService.findAssetById(new AssetId(entityId.getId())));
return;
case DASHBOARD:
checkDashboardId(new DashboardId(entityId.getId()));
return;
case USER:
checkUserId(new UserId(entityId.getId()));
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
}
} catch (Exception e) {
throw handleException(e, false);
}
}
Device checkDeviceId(DeviceId deviceId) throws ThingsboardException { Device checkDeviceId(DeviceId deviceId) throws ThingsboardException {
try { try {
validateId(deviceId, "Incorrect deviceId " + deviceId); validateId(deviceId, "Incorrect deviceId " + deviceId);
@ -264,7 +307,7 @@ public abstract class BaseController {
} }
} }
private void checkDevice(Device device) throws ThingsboardException { protected void checkDevice(Device device) throws ThingsboardException {
checkNotNull(device); checkNotNull(device);
checkTenantId(device.getTenantId()); checkTenantId(device.getTenantId());
if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
@ -272,6 +315,25 @@ public abstract class BaseController {
} }
} }
Asset checkAssetId(AssetId assetId) throws ThingsboardException {
try {
validateId(assetId, "Incorrect assetId " + assetId);
Asset asset = assetService.findAssetById(assetId);
checkAsset(asset);
return asset;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected void checkAsset(Asset asset) throws ThingsboardException {
checkNotNull(asset);
checkTenantId(asset.getTenantId());
if (asset.getCustomerId() != null && !asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(asset.getCustomerId());
}
}
WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException { WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException {
try { try {
validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
@ -318,14 +380,26 @@ public abstract class BaseController {
try { try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId); validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(dashboardId); Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
checkDashboard(dashboard); checkDashboard(dashboard, true);
return dashboard; return dashboard;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e, false); throw handleException(e, false);
} }
} }
private void checkDashboard(Dashboard dashboard) throws ThingsboardException { DashboardInfo checkDashboardInfoId(DashboardId dashboardId) throws ThingsboardException {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
SecurityUser authUser = getCurrentUser();
checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
return dashboardInfo;
} catch (Exception e) {
throw handleException(e, false);
}
}
private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
checkNotNull(dashboard); checkNotNull(dashboard);
checkTenantId(dashboard.getTenantId()); checkTenantId(dashboard.getTenantId());
SecurityUser authUser = getCurrentUser(); SecurityUser authUser = getCurrentUser();
@ -335,7 +409,8 @@ public abstract class BaseController {
ThingsboardErrorCode.PERMISSION_DENIED); ThingsboardErrorCode.PERMISSION_DENIED);
} }
} }
if (dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { if (checkCustomerId &&
dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(dashboard.getCustomerId()); checkCustomerId(dashboard.getCustomerId());
} }
} }
@ -387,6 +462,16 @@ public abstract class BaseController {
return plugin; return plugin;
} }
protected PluginMetaData checkPlugin(PluginId pluginId) throws ThingsboardException {
checkNotNull(pluginId);
return checkPlugin(pluginService.findPluginById(pluginId));
}
protected RuleMetaData checkRule(RuleId ruleId) throws ThingsboardException {
checkNotNull(ruleId);
return checkRule(ruleService.findRuleById(ruleId));
}
protected RuleMetaData checkRule(RuleMetaData rule) throws ThingsboardException { protected RuleMetaData checkRule(RuleMetaData rule) throws ThingsboardException {
checkNotNull(rule); checkNotNull(rule);
SecurityUser authUser = getCurrentUser(); SecurityUser authUser = getCurrentUser();
@ -412,7 +497,8 @@ public abstract class BaseController {
if (request.getHeader("x-forwarded-port") != null) { if (request.getHeader("x-forwarded-port") != null) {
try { try {
serverPort = request.getIntHeader("x-forwarded-port"); serverPort = request.getIntHeader("x-forwarded-port");
} catch (NumberFormatException e) {} } catch (NumberFormatException e) {
}
} }
String baseUrl = String.format("%s://%s:%d", String baseUrl = String.format("%s://%s:%d",

32
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -41,6 +41,19 @@ public class DashboardController extends BaseController {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@ResponseBody
public DashboardInfo getDashboardInfoById(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
checkParameter("dashboardId", strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
return checkDashboardInfoId(dashboardId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -132,6 +145,25 @@ public class DashboardController extends BaseController {
} }
} }
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
public TextPageData<DashboardInfo> getTenantDashboards(
@PathVariable("tenantId") String strTenantId,
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
checkTenantId(tenantId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody @ResponseBody

29
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -24,19 +24,18 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
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.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.DeviceSearchQuery;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException; import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
@ -238,4 +237,28 @@ public class DeviceController extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/devices", method = RequestMethod.POST)
@ResponseBody
public List<Device> findByQuery(@RequestBody DeviceSearchQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkNotNull(query.getDeviceTypes());
checkEntityId(query.getParameters().getEntityId());
try {
List<Device> devices = checkNotNull(deviceService.findDevicesByQuery(query).get());
devices = devices.stream().filter(device -> {
try {
checkDevice(device);
return true;
} catch (ThingsboardException e) {
return false;
}
}).collect(Collectors.toList());
return devices;
} catch (Exception e) {
throw handleException(e);
}
}
} }

194
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java

@ -0,0 +1,194 @@
/**
* Copyright © 2016-2017 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.controller;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import java.util.List;
@RestController
@RequestMapping("/api")
public class EntityRelationController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relation", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void saveRelation(@RequestBody EntityRelation relation) throws ThingsboardException {
try {
checkNotNull(relation);
checkEntityId(relation.getFrom());
checkEntityId(relation.getTo());
relationService.saveRelation(relation).get();
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relation", method = RequestMethod.DELETE, params = {"fromId", "fromType", "relationType", "toId", "toType"})
@ResponseStatus(value = HttpStatus.OK)
public void deleteRelation(@RequestParam("fromId") String strFromId,
@RequestParam("fromType") String strFromType, @RequestParam("relationType") String strRelationType,
@RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
checkParameter("relationType", strRelationType);
checkParameter("toId", strToId);
checkParameter("toType", strToType);
EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(fromId);
checkEntityId(toId);
try {
Boolean found = relationService.deleteRelation(fromId, toId, strRelationType).get();
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.DELETE, params = {"id", "type"})
@ResponseStatus(value = HttpStatus.OK)
public void deleteRelations(@RequestParam("entityId") String strId,
@RequestParam("entityType") String strType) throws ThingsboardException {
checkParameter("entityId", strId);
checkParameter("entityType", strType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strType, strId);
checkEntityId(entityId);
try {
relationService.deleteEntityRelations(entityId).get();
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relation", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType", "toId", "toType"})
@ResponseStatus(value = HttpStatus.OK)
public void checkRelation(@RequestParam("fromId") String strFromId,
@RequestParam("fromType") String strFromType, @RequestParam("relationType") String strRelationType,
@RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
try {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
checkParameter("relationType", strRelationType);
checkParameter("toId", strToId);
checkParameter("toType", strToType);
EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(fromId);
checkEntityId(toId);
Boolean found = relationService.checkRelation(fromId, toId, strRelationType).get();
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType"})
@ResponseBody
public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
checkEntityId(entityId);
try {
return checkNotNull(relationService.findByFrom(entityId).get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"})
@ResponseBody
public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType
, @RequestParam("relationType") String strRelationType) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
checkParameter("relationType", strRelationType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
checkEntityId(entityId);
try {
return checkNotNull(relationService.findByFromAndType(entityId, strRelationType).get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType"})
@ResponseBody
public List<EntityRelation> findByTo(@RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
checkParameter("toId", strToId);
checkParameter("toType", strToType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(entityId);
try {
return checkNotNull(relationService.findByTo(entityId).get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType", "relationType"})
@ResponseBody
public List<EntityRelation> findByTo(@RequestParam("toId") String strToId, @RequestParam("toType") String strToType
, @RequestParam("relationType") String strRelationType) throws ThingsboardException {
checkParameter("toId", strToId);
checkParameter("toType", strToType);
checkParameter("relationType", strRelationType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(entityId);
try {
return checkNotNull(relationService.findByToAndType(entityId, strRelationType).get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.POST)
@ResponseBody
public List<EntityRelation> findByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkNotNull(query.getFilters());
checkEntityId(query.getParameters().getEntityId());
try {
return checkNotNull(relationService.findByQuery(query).get());
} catch (Exception e) {
throw handleException(e);
}
}
}

24
application/src/main/java/org/thingsboard/server/controller/EventController.java

@ -18,7 +18,6 @@ package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
@ -59,7 +58,7 @@ public class EventController extends BaseController {
ThingsboardErrorCode.PERMISSION_DENIED); ThingsboardErrorCode.PERMISSION_DENIED);
} }
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(eventService.findEvents(tenantId, getEntityId(strEntityType, strEntityId), eventType, pageLink)); return checkNotNull(eventService.findEvents(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), eventType, pageLink));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -88,29 +87,10 @@ public class EventController extends BaseController {
ThingsboardErrorCode.PERMISSION_DENIED); ThingsboardErrorCode.PERMISSION_DENIED);
} }
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(eventService.findEvents(tenantId, getEntityId(strEntityType, strEntityId), pageLink)); return checkNotNull(eventService.findEvents(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
} }
private EntityId getEntityId(String strEntityType, String strEntityId) throws ThingsboardException {
EntityId entityId;
EntityType entityType = EntityType.valueOf(strEntityType);
switch (entityType) {
case RULE:
entityId = new RuleId(toUUID(strEntityId));
break;
case PLUGIN:
entityId = new PluginId(toUUID(strEntityId));
break;
case DEVICE:
entityId = new DeviceId(toUUID(strEntityId));
break;
default:
throw new ThingsboardException("EntityType ['" + entityType + "'] is incorrect!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return entityId;
}
} }

6
application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java

@ -15,11 +15,13 @@
*/ */
package org.thingsboard.server.service.cluster.routing; package org.thingsboard.server.service.cluster.routing;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.discovery.ServerInstance; import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
@ -28,6 +30,8 @@ public interface ClusterRoutingService {
ServerAddress getCurrentServer(); ServerAddress getCurrentServer();
Optional<ServerAddress> resolve(UUIDBased entityId); Optional<ServerAddress> resolveByUuid(UUID uuid);
Optional<ServerAddress> resolveById(EntityId entityId);
} }

15
application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
@ -31,6 +32,7 @@ import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
@ -77,13 +79,18 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService, D
} }
@Override @Override
public Optional<ServerAddress> resolve(UUIDBased entityId) { public Optional<ServerAddress> resolveById(EntityId entityId) {
Assert.notNull(entityId); return resolveByUuid(entityId.getId());
}
@Override
public Optional<ServerAddress> resolveByUuid(UUID uuid) {
Assert.notNull(uuid);
if (circle.isEmpty()) { if (circle.isEmpty()) {
return Optional.empty(); return Optional.empty();
} }
Long hash = hashFunction.newHasher().putLong(entityId.getId().getMostSignificantBits()) Long hash = hashFunction.newHasher().putLong(uuid.getMostSignificantBits())
.putLong(entityId.getId().getLeastSignificantBits()).hash().asLong(); .putLong(uuid.getLeastSignificantBits()).hash().asLong();
if (!circle.containsKey(hash)) { if (!circle.containsKey(hash)) {
ConcurrentNavigableMap<Long, ServerInstance> tailMap = ConcurrentNavigableMap<Long, ServerInstance> tailMap =
circle.tailMap(hash); circle.tailMap(hash);

4
application/src/main/resources/thingsboard.yml

@ -60,8 +60,8 @@ plugins:
# JWT Token parameters # JWT Token parameters
security.jwt: security.jwt:
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins) tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000000}" # Number of seconds (15 mins)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour) refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:36000000}" # Seconds (1 hour)
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}" tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}" tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"

4
application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java

@ -145,7 +145,7 @@ public class DefaultActorServiceTest {
ReflectionTestUtils.setField(actorContext, "eventService", eventService); ReflectionTestUtils.setField(actorContext, "eventService", eventService);
when(routingService.resolve(any())).thenReturn(Optional.empty()); when(routingService.resolveById((EntityId) any())).thenReturn(Optional.empty());
when(discoveryService.getCurrentServer()).thenReturn(serverInstance); when(discoveryService.getCurrentServer()).thenReturn(serverInstance);
@ -239,7 +239,7 @@ public class DefaultActorServiceTest {
List<TsKvEntry> expected = new ArrayList<>(); List<TsKvEntry> expected = new ArrayList<>();
expected.add(new BasicTsKvEntry(ts, entry1)); expected.add(new BasicTsKvEntry(ts, entry1));
expected.add(new BasicTsKvEntry(ts, entry2)); expected.add(new BasicTsKvEntry(ts, entry2));
verify(tsService, Mockito.timeout(5000)).save(DataConstants.DEVICE, deviceId, expected); verify(tsService, Mockito.timeout(5000)).save(deviceId, expected);
} }
} }

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

18
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -28,6 +28,7 @@ public class Device extends SearchTextBased<DeviceId> {
private TenantId tenantId; private TenantId tenantId;
private CustomerId customerId; private CustomerId customerId;
private String name; private String name;
private String type;
private JsonNode additionalInfo; private JsonNode additionalInfo;
public Device() { public Device() {
@ -43,6 +44,7 @@ public class Device extends SearchTextBased<DeviceId> {
this.tenantId = device.getTenantId(); this.tenantId = device.getTenantId();
this.customerId = device.getCustomerId(); this.customerId = device.getCustomerId();
this.name = device.getName(); this.name = device.getName();
this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo(); this.additionalInfo = device.getAdditionalInfo();
} }
@ -70,6 +72,14 @@ public class Device extends SearchTextBased<DeviceId> {
this.name = name; this.name = name;
} }
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JsonNode getAdditionalInfo() { public JsonNode getAdditionalInfo() {
return additionalInfo; return additionalInfo;
} }
@ -90,6 +100,7 @@ public class Device extends SearchTextBased<DeviceId> {
result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode()); result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode()); result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result; return result;
} }
@ -118,6 +129,11 @@ public class Device extends SearchTextBased<DeviceId> {
return false; return false;
} else if (!name.equals(other.name)) } else if (!name.equals(other.name))
return false; return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (tenantId == null) { if (tenantId == null) {
if (other.tenantId != null) if (other.tenantId != null)
return false; return false;
@ -135,6 +151,8 @@ public class Device extends SearchTextBased<DeviceId> {
builder.append(customerId); builder.append(customerId);
builder.append(", name="); builder.append(", name=");
builder.append(name); builder.append(name);
builder.append(", type=");
builder.append(type);
builder.append(", additionalInfo="); builder.append(", additionalInfo=");
builder.append(additionalInfo); builder.append(additionalInfo);
builder.append(", createdTime="); builder.append(", createdTime=");

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
public enum EntityType { public enum EntityType {
TENANT, DEVICE, CUSTOMER, RULE, PLUGIN TENANT, CUSTOMER, USER, RULE, PLUGIN, DASHBOARD, ASSET, DEVICE, ALARM
} }

40
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2017 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.common.data.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.EntityId;
/**
* Created by ashvayka on 11.05.17.
*/
@Data
public class Alarm extends BaseData<AlarmId> {
private long startTs;
private long endTs;
private long ackTs;
private long clearTs;
private String type;
private EntityId originator;
private AlarmSeverity severity;
private AlarmStatus status;
private JsonNode details;
private boolean propagate;
}

45
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmId.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2017 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.common.data.alarm;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased;
import java.util.UUID;
public class AlarmId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public AlarmId(@JsonProperty("id") UUID id) {
super(id);
}
public static AlarmId fromString(String alarmId) {
return new AlarmId(UUID.fromString(alarmId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.ALARM;
}
}

32
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2017 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.common.data.alarm;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageLink;
/**
* Created by ashvayka on 11.05.17.
*/
@Data
public class AlarmQuery {
private EntityId affectedEntityId;
private TimePageLink pageLink;
private AlarmStatus status;
}

25
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSeverity.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2017 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.common.data.alarm;
/**
* Created by ashvayka on 11.05.17.
*/
public enum AlarmSeverity {
CRITICAL, MAJOR, MINOR, WARNING, INDETERMINATE;
}

25
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2017 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.common.data.alarm;
/**
* Created by ashvayka on 11.05.17.
*/
public enum AlarmStatus {
ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK;
}

166
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -0,0 +1,166 @@
/**
* Copyright © 2016-2017 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.common.data.asset;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
public class Asset extends SearchTextBased<AssetId> {
private static final long serialVersionUID = 2807343040519543363L;
private TenantId tenantId;
private CustomerId customerId;
private String name;
private String type;
private JsonNode additionalInfo;
public Asset() {
super();
}
public Asset(AssetId id) {
super(id);
}
public Asset(Asset asset) {
super(asset);
this.tenantId = asset.getTenantId();
this.customerId = asset.getCustomerId();
this.name = asset.getName();
this.type = asset.getType();
this.additionalInfo = asset.getAdditionalInfo();
}
public TenantId getTenantId() {
return tenantId;
}
public void setTenantId(TenantId tenantId) {
this.tenantId = tenantId;
}
public CustomerId getCustomerId() {
return customerId;
}
public void setCustomerId(CustomerId customerId) {
this.customerId = customerId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
public void setAdditionalInfo(JsonNode additionalInfo) {
this.additionalInfo = additionalInfo;
}
@Override
public String getSearchText() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Asset other = (Asset) obj;
if (additionalInfo == null) {
if (other.additionalInfo != null)
return false;
} else if (!additionalInfo.equals(other.additionalInfo))
return false;
if (customerId == null) {
if (other.customerId != null)
return false;
} else if (!customerId.equals(other.customerId))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
} else if (!tenantId.equals(other.tenantId))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Asset [tenantId=");
builder.append(tenantId);
builder.append(", customerId=");
builder.append(customerId);
builder.append(", name=");
builder.append(name);
builder.append(", type=");
builder.append(type);
builder.append(", additionalInfo=");
builder.append(additionalInfo);
builder.append(", createdTime=");
builder.append(createdTime);
builder.append(", id=");
builder.append(id);
builder.append("]");
return builder.toString();
}
}

43
common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2017 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.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class AssetId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public AssetId(@JsonProperty("id") UUID id) {
super(id);
}
public static AssetId fromString(String assetId) {
return new AssetId(UUID.fromString(assetId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.ASSET;
}
}

16
common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java

@ -18,12 +18,24 @@ package org.thingsboard.server.common.data.id;
import java.util.UUID; import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
public class DashboardId extends UUIDBased { public class DashboardId extends UUIDBased implements EntityId {
@JsonCreator @JsonCreator
public DashboardId(@JsonProperty("id") UUID id){ public DashboardId(@JsonProperty("id") UUID id) {
super(id); super(id);
} }
public static DashboardId fromString(String dashboardId) {
return new DashboardId(UUID.fromString(dashboardId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.DASHBOARD;
}
} }

5
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java

@ -16,6 +16,8 @@
package org.thingsboard.server.common.data.id; package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -23,6 +25,9 @@ import java.util.UUID;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
@JsonDeserialize(using = EntityIdDeserializer.class)
@JsonSerialize(using = EntityIdSerializer.class)
public interface EntityId { public interface EntityId {
UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080"); UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080");

43
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdDeserializer.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2017 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.common.data.id;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
/**
* Created by ashvayka on 11.05.17.
*/
public class EntityIdDeserializer extends JsonDeserializer<EntityId> {
@Override
public EntityId deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
ObjectNode node = oc.readTree(jsonParser);
if (node.has("entityType") && node.has("id")) {
return EntityIdFactory.getByTypeAndId(node.get("entityType").asText(), node.get("id").asText());
} else {
throw new IOException("Missing entityType or id!");
}
}
}

56
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2017 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.common.data.id;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
/**
* Created by ashvayka on 25.04.17.
*/
public class EntityIdFactory {
public static EntityId getByTypeAndId(String type, String uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), UUID.fromString(uuid));
}
public static EntityId getByTypeAndUuid(String type, UUID uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), uuid);
}
public static EntityId getByTypeAndUuid(EntityType type, UUID uuid) {
switch (type) {
case TENANT:
return new TenantId(uuid);
case CUSTOMER:
return new CustomerId(uuid);
case USER:
return new UserId(uuid);
case RULE:
return new RuleId(uuid);
case PLUGIN:
return new PluginId(uuid);
case DASHBOARD:
return new DashboardId(uuid);
case DEVICE:
return new DeviceId(uuid);
case ASSET:
return new AssetId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}
}

37
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdSerializer.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2017 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.common.data.id;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* Created by ashvayka on 11.05.17.
*/
public class EntityIdSerializer extends JsonSerializer<EntityId> {
@Override
public void serialize(EntityId value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeStringField("entityType", value.getEntityType().name());
gen.writeStringField("id", value.getId().toString());
gen.writeEndObject();
}
}

21
common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java

@ -18,12 +18,25 @@ package org.thingsboard.server.common.data.id;
import java.util.UUID; import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
public class UserId extends UUIDBased { public class UserId extends UUIDBased implements EntityId {
@JsonCreator @JsonCreator
public UserId(@JsonProperty("id") UUID id){ public UserId(@JsonProperty("id") UUID id) {
super(id); super(id);
} }
public static UserId fromString(String userId) {
return new UserId(UUID.fromString(userId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.USER;
}
} }

103
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java

@ -0,0 +1,103 @@
/**
* Copyright © 2016-2017 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.common.data.relation;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Objects;
public class EntityRelation {
private static final long serialVersionUID = 2807343040519543363L;
public static final String CONTAINS_TYPE = "Contains";
public static final String MANAGES_TYPE = "Manages";
private EntityId from;
private EntityId to;
private String type;
private JsonNode additionalInfo;
public EntityRelation() {
super();
}
public EntityRelation(EntityId from, EntityId to, String type) {
this(from, to, type, null);
}
public EntityRelation(EntityId from, EntityId to, String type, JsonNode additionalInfo) {
this.from = from;
this.to = to;
this.type = type;
this.additionalInfo = additionalInfo;
}
public EntityRelation(EntityRelation device) {
this.from = device.getFrom();
this.to = device.getTo();
this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo();
}
public EntityId getFrom() {
return from;
}
public void setFrom(EntityId from) {
this.from = from;
}
public EntityId getTo() {
return to;
}
public void setTo(EntityId to) {
this.to = to;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
public void setAdditionalInfo(JsonNode additionalInfo) {
this.additionalInfo = additionalInfo;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EntityRelation relation = (EntityRelation) o;
return Objects.equals(from, relation.from) &&
Objects.equals(to, relation.to) &&
Objects.equals(type, relation.type);
}
@Override
public int hashCode() {
return Objects.hash(from, to, type);
}
}

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

22
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2017 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.dao.alarm;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmDao {
}

41
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2017 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.dao.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.page.TimePageData;
import java.util.Optional;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmService {
Optional<Alarm> saveIfNotExists(Alarm alarm);
ListenableFuture<Boolean> updateAlarm(Alarm alarm);
ListenableFuture<Boolean> ackAlarm(Alarm alarm);
ListenableFuture<Boolean> clearAlarm(AlarmId alarmId);
ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
}

90
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java

@ -0,0 +1,90 @@
/**
* Copyright © 2016-2017 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.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.model.AssetEntity;
import org.thingsboard.server.dao.model.DeviceEntity;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* The Interface AssetDao.
*
*/
public interface AssetDao extends Dao<AssetEntity> {
/**
* Save or update asset object
*
* @param asset the asset object
* @return saved asset object
*/
AssetEntity save(Asset asset);
/**
* Find assets by tenantId and page link.
*
* @param tenantId the tenantId
* @param pageLink the page link
* @return the list of asset objects
*/
List<AssetEntity> findAssetsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find assets by tenantId and assets Ids.
*
* @param tenantId the tenantId
* @param assetIds the asset Ids
* @return the list of asset objects
*/
ListenableFuture<List<AssetEntity>> findAssetsByTenantIdAndIdsAsync(UUID tenantId, List<UUID> assetIds);
/**
* Find assets by tenantId, customerId and page link.
*
* @param tenantId the tenantId
* @param customerId the customerId
* @param pageLink the page link
* @return the list of asset objects
*/
List<AssetEntity> findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
/**
* Find assets by tenantId, customerId and assets Ids.
*
* @param tenantId the tenantId
* @param customerId the customerId
* @param assetIds the asset Ids
* @return the list of asset objects
*/
ListenableFuture<List<AssetEntity>> findAssetsByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> assetIds);
/**
* Find assets by tenantId and asset name.
*
* @param tenantId the tenantId
* @param name the asset name
* @return the optional asset object
*/
Optional<AssetEntity> findAssetsByTenantIdAndName(UUID tenantId, String name);
}

104
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java

@ -0,0 +1,104 @@
/**
* Copyright © 2016-2017 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.dao.asset;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.AbstractSearchTextDao;
import org.thingsboard.server.dao.model.AssetEntity;
import java.util.*;
import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
public class AssetDaoImpl extends AbstractSearchTextDao<AssetEntity> implements AssetDao {
@Override
protected Class<AssetEntity> getColumnFamilyClass() {
return AssetEntity.class;
}
@Override
protected String getColumnFamilyName() {
return ASSET_COLUMN_FAMILY_NAME;
}
@Override
public AssetEntity save(Asset asset) {
log.debug("Save asset [{}] ", asset);
return save(new AssetEntity(asset));
}
@Override
public List<AssetEntity> findAssetsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find assets by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<AssetEntity> assetEntities = findPageWithTextSearch(ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Collections.singletonList(eq(ASSET_TENANT_ID_PROPERTY, tenantId)), pageLink);
log.trace("Found assets [{}] by tenantId [{}] and pageLink [{}]", assetEntities, tenantId, pageLink);
return assetEntities;
}
@Override
public ListenableFuture<List<AssetEntity>> findAssetsByTenantIdAndIdsAsync(UUID tenantId, List<UUID> assetIds) {
log.debug("Try to find assets by tenantId [{}] and asset Ids [{}]", tenantId, assetIds);
Select select = select().from(getColumnFamilyName());
Select.Where query = select.where();
query.and(eq(ASSET_TENANT_ID_PROPERTY, tenantId));
query.and(in(ID_PROPERTY, assetIds));
return findListByStatementAsync(query);
}
@Override
public List<AssetEntity> findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
log.debug("Try to find assets by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
List<AssetEntity> assetEntities = findPageWithTextSearch(ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Arrays.asList(eq(ASSET_CUSTOMER_ID_PROPERTY, customerId),
eq(ASSET_TENANT_ID_PROPERTY, tenantId)),
pageLink);
log.trace("Found assets [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", assetEntities, tenantId, customerId, pageLink);
return assetEntities;
}
@Override
public ListenableFuture<List<AssetEntity>> findAssetsByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> assetIds) {
log.debug("Try to find assets by tenantId [{}], customerId [{}] and asset Ids [{}]", tenantId, customerId, assetIds);
Select select = select().from(getColumnFamilyName());
Select.Where query = select.where();
query.and(eq(ASSET_TENANT_ID_PROPERTY, tenantId));
query.and(eq(ASSET_CUSTOMER_ID_PROPERTY, customerId));
query.and(in(ID_PROPERTY, assetIds));
return findListByStatementAsync(query);
}
@Override
public Optional<AssetEntity> findAssetsByTenantIdAndName(UUID tenantId, String assetName) {
Select select = select().from(ASSET_BY_TENANT_AND_NAME_VIEW_NAME);
Select.Where query = select.where();
query.and(eq(ASSET_TENANT_ID_PROPERTY, tenantId));
query.and(eq(ASSET_NAME_PROPERTY, assetName));
return Optional.ofNullable(findOneByStatement(query));
}
}

50
dao/src/main/java/org/thingsboard/server/dao/asset/AssetSearchQuery.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2017 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.dao.asset;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntityTypeFilter;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by ashvayka on 03.05.17.
*/
@Data
public class AssetSearchQuery {
private RelationsSearchParameters parameters;
@Nullable
private String relationType;
@Nullable
private List<String> assetTypes;
public EntityRelationsQuery toEntitySearchQuery() {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.ASSET))));
return query;
}
}

59
dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2017 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.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import java.util.List;
import java.util.Optional;
public interface AssetService {
Asset findAssetById(AssetId assetId);
ListenableFuture<Asset> findAssetByIdAsync(AssetId assetId);
Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name);
Asset saveAsset(Asset device);
Asset assignAssetToCustomer(AssetId assetId, CustomerId customerId);
Asset unassignAssetFromCustomer(AssetId assetId);
void deleteAsset(AssetId assetId);
TextPageData<Asset> findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink);
ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
void deleteAssetsByTenantId(TenantId tenantId);
TextPageData<Asset> findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds);
void unassignCustomerAssets(TenantId tenantId, CustomerId customerId);
ListenableFuture<List<Asset>> findAssetsByQuery(AssetSearchQuery query);
}

32
dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2017 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.dao.asset;
import lombok.Data;
import javax.annotation.Nullable;
import java.util.List;
/**
* Created by ashvayka on 02.05.17.
*/
@Data
public class AssetTypeFilter {
@Nullable
private String relationType;
@Nullable
private List<String> assetTypes;
}

292
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -0,0 +1,292 @@
/**
* Copyright © 2016-2017 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.dao.asset;
import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.AssetEntity;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.*;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
public class BaseAssetService extends BaseEntityService implements AssetService {
@Autowired
private AssetDao assetDao;
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@Override
public Asset findAssetById(AssetId assetId) {
log.trace("Executing findAssetById [{}]", assetId);
validateId(assetId, "Incorrect assetId " + assetId);
AssetEntity assetEntity = assetDao.findById(assetId.getId());
return getData(assetEntity);
}
@Override
public ListenableFuture<Asset> findAssetByIdAsync(AssetId assetId) {
log.trace("Executing findAssetById [{}]", assetId);
validateId(assetId, "Incorrect assetId " + assetId);
ListenableFuture<AssetEntity> assetEntity = assetDao.findByIdAsync(assetId.getId());
return Futures.transform(assetEntity, (Function<? super AssetEntity, ? extends Asset>) input -> getData(input));
}
@Override
public Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name) {
log.trace("Executing findAssetByTenantIdAndName [{}][{}]", tenantId, name);
validateId(tenantId, "Incorrect tenantId " + tenantId);
Optional<AssetEntity> assetEntityOpt = assetDao.findAssetsByTenantIdAndName(tenantId.getId(), name);
if (assetEntityOpt.isPresent()) {
return Optional.of(getData(assetEntityOpt.get()));
} else {
return Optional.empty();
}
}
@Override
public Asset saveAsset(Asset asset) {
log.trace("Executing saveAsset [{}]", asset);
assetValidator.validate(asset);
return getData(assetDao.save(asset));
}
@Override
public Asset assignAssetToCustomer(AssetId assetId, CustomerId customerId) {
Asset asset = findAssetById(assetId);
asset.setCustomerId(customerId);
return saveAsset(asset);
}
@Override
public Asset unassignAssetFromCustomer(AssetId assetId) {
Asset asset = findAssetById(assetId);
asset.setCustomerId(null);
return saveAsset(asset);
}
@Override
public void deleteAsset(AssetId assetId) {
log.trace("Executing deleteAsset [{}]", assetId);
validateId(assetId, "Incorrect assetId " + assetId);
deleteEntityRelations(assetId);
assetDao.removeById(assetId.getId());
}
@Override
public TextPageData<Asset> findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink) {
log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, "Incorrect tenantId " + tenantId);
validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<AssetEntity> assetEntities = assetDao.findAssetsByTenantId(tenantId.getId(), pageLink);
List<Asset> assets = convertDataList(assetEntities);
return new TextPageData<Asset>(assets, pageLink);
}
@Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdAndIdsAsync, tenantId [{}], assetIds [{}]", tenantId, assetIds);
validateId(tenantId, "Incorrect tenantId " + tenantId);
validateIds(assetIds, "Incorrect assetIds " + assetIds);
ListenableFuture<List<AssetEntity>> assetEntities = assetDao.findAssetsByTenantIdAndIdsAsync(tenantId.getId(), toUUIDs(assetIds));
return Futures.transform(assetEntities, (Function<List<AssetEntity>, List<Asset>>) input -> convertDataList(input));
}
@Override
public void deleteAssetsByTenantId(TenantId tenantId) {
log.trace("Executing deleteAssetsByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, "Incorrect tenantId " + tenantId);
tenantAssetsRemover.removeEntitites(tenantId);
}
@Override
public TextPageData<Asset> findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
log.trace("Executing findAssetsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
validateId(tenantId, "Incorrect tenantId " + tenantId);
validateId(customerId, "Incorrect customerId " + customerId);
validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<AssetEntity> assetEntities = assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
List<Asset> assets = convertDataList(assetEntities);
return new TextPageData<Asset>(assets, pageLink);
}
@Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], assetIds [{}]", tenantId, customerId, assetIds);
validateId(tenantId, "Incorrect tenantId " + tenantId);
validateId(customerId, "Incorrect customerId " + customerId);
validateIds(assetIds, "Incorrect assetIds " + assetIds);
ListenableFuture<List<AssetEntity>> assetEntities = assetDao.findAssetsByTenantIdCustomerIdAndIdsAsync(tenantId.getId(),
customerId.getId(), toUUIDs(assetIds));
return Futures.transform(assetEntities, (Function<List<AssetEntity>, List<Asset>>) input -> convertDataList(input));
}
@Override
public void unassignCustomerAssets(TenantId tenantId, CustomerId customerId) {
log.trace("Executing unassignCustomerAssets, tenantId [{}], customerId [{}]", tenantId, customerId);
validateId(tenantId, "Incorrect tenantId " + tenantId);
validateId(customerId, "Incorrect customerId " + customerId);
new CustomerAssetsUnassigner(tenantId).removeEntitites(customerId);
}
@Override
public ListenableFuture<List<Asset>> findAssetsByQuery(AssetSearchQuery query) {
ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
ListenableFuture<List<Asset>> assets = Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Asset>>) relations1 -> {
EntitySearchDirection direction = query.toEntitySearchQuery().getParameters().getDirection();
List<ListenableFuture<Asset>> futures = new ArrayList<>();
for (EntityRelation relation : relations1) {
EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom();
if (entityId.getEntityType() == EntityType.ASSET) {
futures.add(findAssetByIdAsync(new AssetId(entityId.getId())));
}
}
return Futures.successfulAsList(futures);
});
assets = Futures.transform(assets, new Function<List<Asset>, List<Asset>>() {
@Nullable
@Override
public List<Asset> apply(@Nullable List<Asset> assetList) {
return assetList.stream().filter(asset -> query.getAssetTypes().contains(asset.getType())).collect(Collectors.toList());
}
});
return assets;
}
private DataValidator<Asset> assetValidator =
new DataValidator<Asset>() {
@Override
protected void validateCreate(Asset asset) {
assetDao.findAssetsByTenantIdAndName(asset.getTenantId().getId(), asset.getName()).ifPresent(
d -> {
throw new DataValidationException("Asset with such name already exists!");
}
);
}
@Override
protected void validateUpdate(Asset asset) {
assetDao.findAssetsByTenantIdAndName(asset.getTenantId().getId(), asset.getName()).ifPresent(
d -> {
if (!d.getId().equals(asset.getUuidId())) {
throw new DataValidationException("Asset with such name already exists!");
}
}
);
}
@Override
protected void validateDataImpl(Asset asset) {
if (StringUtils.isEmpty(asset.getName())) {
throw new DataValidationException("Asset name should be specified!");
}
if (asset.getTenantId() == null) {
throw new DataValidationException("Asset should be assigned to tenant!");
} else {
TenantEntity tenant = tenantDao.findById(asset.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Asset is referencing to non-existent tenant!");
}
}
if (asset.getCustomerId() == null) {
asset.setCustomerId(new CustomerId(NULL_UUID));
} else if (!asset.getCustomerId().getId().equals(NULL_UUID)) {
CustomerEntity customer = customerDao.findById(asset.getCustomerId().getId());
if (customer == null) {
throw new DataValidationException("Can't assign asset to non-existent customer!");
}
if (!customer.getTenantId().equals(asset.getTenantId().getId())) {
throw new DataValidationException("Can't assign asset to customer from different tenant!");
}
}
}
};
private PaginatedRemover<TenantId, AssetEntity> tenantAssetsRemover =
new PaginatedRemover<TenantId, AssetEntity>() {
@Override
protected List<AssetEntity> findEntities(TenantId id, TextPageLink pageLink) {
return assetDao.findAssetsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(AssetEntity entity) {
deleteAsset(new AssetId(entity.getId()));
}
};
class CustomerAssetsUnassigner extends PaginatedRemover<CustomerId, AssetEntity> {
private TenantId tenantId;
CustomerAssetsUnassigner(TenantId tenantId) {
this.tenantId = tenantId;
}
@Override
protected List<AssetEntity> findEntities(CustomerId id, TextPageLink pageLink) {
return assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
}
@Override
protected void removeEntity(AssetEntity entity) {
unassignAssetFromCustomer(new AssetId(entity.getId()));
}
}
}

17
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.customer; package org.thingsboard.server.dao.customer;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -23,16 +24,18 @@ import org.thingsboard.server.common.data.page.TextPageLink;
public interface CustomerService { public interface CustomerService {
public Customer findCustomerById(CustomerId customerId); Customer findCustomerById(CustomerId customerId);
ListenableFuture<Customer> findCustomerByIdAsync(CustomerId customerId);
public Customer saveCustomer(Customer customer); Customer saveCustomer(Customer customer);
public void deleteCustomer(CustomerId customerId); void deleteCustomer(CustomerId customerId);
public Customer findOrCreatePublicCustomer(TenantId tenantId); Customer findOrCreatePublicCustomer(TenantId tenantId);
public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink);
public void deleteCustomersByTenantId(TenantId tenantId); TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink);
void deleteCustomersByTenantId(TenantId tenantId);
} }

22
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.customer;
import static org.thingsboard.server.dao.DaoUtil.convertDataList; import static org.thingsboard.server.dao.DaoUtil.convertDataList;
import static org.thingsboard.server.dao.DaoUtil.getData; import static org.thingsboard.server.dao.DaoUtil.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -24,31 +25,35 @@ import java.util.Optional;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.AssetEntity;
import org.thingsboard.server.dao.model.CustomerEntity; import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.TenantEntity; import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
@Service @Service
@Slf4j @Slf4j
public class CustomerServiceImpl implements CustomerService { public class CustomerServiceImpl extends BaseEntityService implements CustomerService {
private static final String PUBLIC_CUSTOMER_TITLE = "Public"; private static final String PUBLIC_CUSTOMER_TITLE = "Public";
@ -75,6 +80,14 @@ public class CustomerServiceImpl implements CustomerService {
return getData(customerEntity); return getData(customerEntity);
} }
@Override
public ListenableFuture<Customer> findCustomerByIdAsync(CustomerId customerId) {
log.trace("Executing findCustomerByIdAsync [{}]", customerId);
validateId(customerId, "Incorrect customerId " + customerId);
ListenableFuture<CustomerEntity> customerEntity = customerDao.findByIdAsync(customerId.getId());
return Futures.transform(customerEntity, (Function<? super CustomerEntity, ? extends Customer>) input -> getData(input));
}
@Override @Override
public Customer saveCustomer(Customer customer) { public Customer saveCustomer(Customer customer) {
log.trace("Executing saveCustomer [{}]", customer); log.trace("Executing saveCustomer [{}]", customer);
@ -93,7 +106,8 @@ public class CustomerServiceImpl implements CustomerService {
} }
dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId); dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
deviceService.unassignCustomerDevices(customer.getTenantId(), customerId); deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
userService.deleteCustomerUsers(customer.getTenantId(), customerId); userService.deleteCustomerUsers(customer.getTenantId(), customerId);
deleteEntityRelations(customerId);
customerDao.removeById(customerId.getId()); customerDao.removeById(customerId.getId());
} }

4
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -26,7 +26,9 @@ import org.thingsboard.server.common.data.page.TextPageLink;
public interface DashboardService { public interface DashboardService {
public Dashboard findDashboardById(DashboardId dashboardId); public Dashboard findDashboardById(DashboardId dashboardId);
public DashboardInfo findDashboardInfoById(DashboardId dashboardId);
public Dashboard saveDashboard(Dashboard dashboard); public Dashboard saveDashboard(Dashboard dashboard);
public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId); public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);

14
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -30,20 +30,19 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.*; import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
@Service @Service
@Slf4j @Slf4j
public class DashboardServiceImpl implements DashboardService { public class DashboardServiceImpl extends BaseEntityService implements DashboardService {
@Autowired @Autowired
private DashboardDao dashboardDao; private DashboardDao dashboardDao;
@ -65,6 +64,14 @@ public class DashboardServiceImpl implements DashboardService {
return getData(dashboardEntity); return getData(dashboardEntity);
} }
@Override
public DashboardInfo findDashboardInfoById(DashboardId dashboardId) {
log.trace("Executing findDashboardInfoById [{}]", dashboardId);
Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfoEntity dashboardInfoEntity = dashboardInfoDao.findById(dashboardId.getId());
return getData(dashboardInfoEntity);
}
@Override @Override
public Dashboard saveDashboard(Dashboard dashboard) { public Dashboard saveDashboard(Dashboard dashboard) {
log.trace("Executing saveDashboard [{}]", dashboard); log.trace("Executing saveDashboard [{}]", dashboard);
@ -91,6 +98,7 @@ public class DashboardServiceImpl implements DashboardService {
public void deleteDashboard(DashboardId dashboardId) { public void deleteDashboard(DashboardId dashboardId) {
log.trace("Executing deleteDashboard [{}]", dashboardId); log.trace("Executing deleteDashboard [{}]", dashboardId);
Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId); Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
deleteEntityRelations(dashboardId);
dashboardDao.removeById(dashboardId.getId()); dashboardDao.removeById(dashboardId.getId());
} }

46
dao/src/main/java/org/thingsboard/server/dao/device/DeviceSearchQuery.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2017 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.dao.device;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntityTypeFilter;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
@Data
public class DeviceSearchQuery {
private RelationsSearchParameters parameters;
@Nullable
private String relationType;
@Nullable
private List<String> deviceTypes;
public EntityRelationsQuery toEntitySearchQuery() {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.DEVICE))));
return query;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -53,4 +53,7 @@ public interface DeviceService {
ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds); ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds);
void unassignCustomerDevices(TenantId tenantId, CustomerId customerId); void unassignCustomerDevices(TenantId tenantId, CustomerId customerId);
ListenableFuture<List<Device>> findDevicesByQuery(DeviceSearchQuery query);
} }

46
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.device; package org.thingsboard.server.dao.device;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
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 lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,36 +25,40 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
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.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
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.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity; import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.DeviceEntity; import org.thingsboard.server.dao.model.DeviceEntity;
import org.thingsboard.server.dao.model.TenantEntity; import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.convertDataList; import static org.thingsboard.server.dao.DaoUtil.*;
import static org.thingsboard.server.dao.DaoUtil.getData;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.*;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service @Service
@Slf4j @Slf4j
public class DeviceServiceImpl implements DeviceService { public class DeviceServiceImpl extends BaseEntityService implements DeviceService {
@Autowired @Autowired
private DeviceDao deviceDao; private DeviceDao deviceDao;
@ -132,6 +137,7 @@ public class DeviceServiceImpl implements DeviceService {
if (deviceCredentials != null) { if (deviceCredentials != null) {
deviceCredentialsService.deleteDeviceCredentials(deviceCredentials); deviceCredentialsService.deleteDeviceCredentials(deviceCredentials);
} }
deleteEntityRelations(deviceId);
deviceDao.removeById(deviceId.getId()); deviceDao.removeById(deviceId.getId());
} }
@ -192,6 +198,32 @@ public class DeviceServiceImpl implements DeviceService {
new CustomerDevicesUnassigner(tenantId).removeEntitites(customerId); new CustomerDevicesUnassigner(tenantId).removeEntitites(customerId);
} }
@Override
public ListenableFuture<List<Device>> findDevicesByQuery(DeviceSearchQuery query) {
ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
ListenableFuture<List<Device>> devices = Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Device>>) relations1 -> {
EntitySearchDirection direction = query.toEntitySearchQuery().getParameters().getDirection();
List<ListenableFuture<Device>> futures = new ArrayList<>();
for (EntityRelation relation : relations1) {
EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom();
if (entityId.getEntityType() == EntityType.DEVICE) {
futures.add(findDeviceByIdAsync(new DeviceId(entityId.getId())));
}
}
return Futures.successfulAsList(futures);
});
devices = Futures.transform(devices, new Function<List<Device>, List<Device>>() {
@Nullable
@Override
public List<Device> apply(@Nullable List<Device> deviceList) {
return deviceList.stream().filter(device -> query.getDeviceTypes().contains(device.getType())).collect(Collectors.toList());
}
});
return devices;
}
private DataValidator<Device> deviceValidator = private DataValidator<Device> deviceValidator =
new DataValidator<Device>() { new DataValidator<Device>() {

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

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2017 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.dao.entity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.relation.RelationService;
/**
* Created by ashvayka on 04.05.17.
*/
@Slf4j
public class BaseEntityService {
@Autowired
protected RelationService relationService;
protected void deleteEntityRelations(EntityId entityId) {
log.trace("Executing deleteEntityRelations [{}]", entityId);
relationService.deleteEntityRelations(entityId);
}
}

235
dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java

@ -0,0 +1,235 @@
/**
* Copyright © 2016-2017 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.dao.model;
import com.datastax.driver.core.utils.UUIDs;
import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
import com.datastax.driver.mapping.annotations.Transient;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.type.JsonCodec;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@Table(name = ASSET_COLUMN_FAMILY_NAME)
public final class AssetEntity implements SearchTextEntity<Asset> {
@Transient
private static final long serialVersionUID = -1265181166886910152L;
@PartitionKey(value = 0)
@Column(name = ID_PROPERTY)
private UUID id;
@PartitionKey(value = 1)
@Column(name = ASSET_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = ASSET_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = ASSET_NAME_PROPERTY)
private String name;
@Column(name = ASSET_TYPE_PROPERTY)
private String type;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = ASSET_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
private JsonNode additionalInfo;
public AssetEntity() {
super();
}
public AssetEntity(Asset asset) {
if (asset.getId() != null) {
this.id = asset.getId().getId();
}
if (asset.getTenantId() != null) {
this.tenantId = asset.getTenantId().getId();
}
if (asset.getCustomerId() != null) {
this.customerId = asset.getCustomerId().getId();
}
this.name = asset.getName();
this.type = asset.getType();
this.additionalInfo = asset.getAdditionalInfo();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
public void setAdditionalInfo(JsonNode additionalInfo) {
this.additionalInfo = additionalInfo;
}
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
public String getSearchText() {
return searchText;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AssetEntity other = (AssetEntity) obj;
if (additionalInfo == null) {
if (other.additionalInfo != null)
return false;
} else if (!additionalInfo.equals(other.additionalInfo))
return false;
if (customerId == null) {
if (other.customerId != null)
return false;
} else if (!customerId.equals(other.customerId))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
} else if (!tenantId.equals(other.tenantId))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("AssetEntity [id=");
builder.append(id);
builder.append(", tenantId=");
builder.append(tenantId);
builder.append(", customerId=");
builder.append(customerId);
builder.append(", name=");
builder.append(name);
builder.append(", type=");
builder.append(type);
builder.append(", additionalInfo=");
builder.append(additionalInfo);
builder.append("]");
return builder.toString();
}
@Override
public Asset toData() {
Asset asset = new Asset(new AssetId(id));
asset.setCreatedTime(UUIDs.unixTimestamp(id));
if (tenantId != null) {
asset.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
asset.setCustomerId(new CustomerId(customerId));
}
asset.setName(name);
asset.setType(type);
asset.setAdditionalInfo(additionalInfo);
return asset;
}
}

23
dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java

@ -51,7 +51,10 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
@Column(name = DEVICE_NAME_PROPERTY) @Column(name = DEVICE_NAME_PROPERTY)
private String name; private String name;
@Column(name = DEVICE_TYPE_PROPERTY)
private String type;
@Column(name = SEARCH_TEXT_PROPERTY) @Column(name = SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@ -73,6 +76,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
this.customerId = device.getCustomerId().getId(); this.customerId = device.getCustomerId().getId();
} }
this.name = device.getName(); this.name = device.getName();
this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo(); this.additionalInfo = device.getAdditionalInfo();
} }
@ -108,6 +112,14 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
this.name = name; this.name = name;
} }
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JsonNode getAdditionalInfo() { public JsonNode getAdditionalInfo() {
return additionalInfo; return additionalInfo;
} }
@ -138,6 +150,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode()); result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result; return result;
} }
@ -171,6 +184,11 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
return false; return false;
} else if (!name.equals(other.name)) } else if (!name.equals(other.name))
return false; return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (tenantId == null) { if (tenantId == null) {
if (other.tenantId != null) if (other.tenantId != null)
return false; return false;
@ -190,6 +208,8 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
builder.append(customerId); builder.append(customerId);
builder.append(", name="); builder.append(", name=");
builder.append(name); builder.append(name);
builder.append(", type=");
builder.append(type);
builder.append(", additionalInfo="); builder.append(", additionalInfo=");
builder.append(additionalInfo); builder.append(additionalInfo);
builder.append("]"); builder.append("]");
@ -207,6 +227,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
device.setCustomerId(new CustomerId(customerId)); device.setCustomerId(new CustomerId(customerId));
} }
device.setName(name); device.setName(name);
device.setType(type);
device.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
return device; return device;
} }

20
dao/src/main/java/org/thingsboard/server/dao/model/EventEntity.java

@ -35,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
*/ */
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@Table(name = DEVICE_COLUMN_FAMILY_NAME) @Table(name = EVENT_COLUMN_FAMILY_NAME)
public class EventEntity implements BaseEntity<Event> { public class EventEntity implements BaseEntity<Event> {
@Transient @Transient
@ -98,23 +98,7 @@ public class EventEntity implements BaseEntity<Event> {
Event event = new Event(new EventId(id)); Event event = new Event(new EventId(id));
event.setCreatedTime(UUIDs.unixTimestamp(id)); event.setCreatedTime(UUIDs.unixTimestamp(id));
event.setTenantId(new TenantId(tenantId)); event.setTenantId(new TenantId(tenantId));
switch (entityType) { event.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
case TENANT:
event.setEntityId(new TenantId(entityId));
break;
case DEVICE:
event.setEntityId(new DeviceId(entityId));
break;
case CUSTOMER:
event.setEntityId(new CustomerId(entityId));
break;
case RULE:
event.setEntityId(new RuleId(entityId));
break;
case PLUGIN:
event.setEntityId(new PluginId(entityId));
break;
}
event.setBody(body); event.setBody(body);
event.setType(eventType); event.setType(eventType);
event.setUid(eventUId); event.setUid(eventUId);

27
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -120,12 +120,39 @@ public class ModelConstants {
public static final String DEVICE_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY; public static final String DEVICE_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String DEVICE_NAME_PROPERTY = "name"; public static final String DEVICE_NAME_PROPERTY = "name";
public static final String DEVICE_TYPE_PROPERTY = "type";
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text"; public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name"; public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name";
/**
* Cassandra asset constants.
*/
public static final String ASSET_COLUMN_FAMILY_NAME = "asset";
public static final String ASSET_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
public static final String ASSET_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ASSET_NAME_PROPERTY = "name";
public static final String ASSET_TYPE_PROPERTY = "type";
public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text";
public static final String ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_and_search_text";
public static final String ASSET_BY_TENANT_AND_NAME_VIEW_NAME = "asset_by_tenant_and_name";
/**
* Cassandra entity relation constants.
*/
public static final String RELATION_COLUMN_FAMILY_NAME = "relation";
public static final String RELATION_FROM_ID_PROPERTY = "from_id";
public static final String RELATION_FROM_TYPE_PROPERTY = "from_type";
public static final String RELATION_TO_ID_PROPERTY = "to_id";
public static final String RELATION_TO_TYPE_PROPERTY = "to_type";
public static final String RELATION_TYPE_PROPERTY = "relation_type";
public static final String RELATION_REVERSE_VIEW_NAME = "reverse_relation";
/** /**
* Cassandra device_credentials constants. * Cassandra device_credentials constants.

18
dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java

@ -15,13 +15,14 @@
*/ */
package org.thingsboard.server.dao.plugin; package org.thingsboard.server.dao.plugin;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
@ -29,7 +30,9 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService; import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException; import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -48,10 +51,11 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.convertDataList; import static org.thingsboard.server.dao.DaoUtil.convertDataList;
import static org.thingsboard.server.dao.DaoUtil.getData; import static org.thingsboard.server.dao.DaoUtil.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service @Service
@Slf4j @Slf4j
public class BasePluginService implements PluginService { public class BasePluginService extends BaseEntityService implements PluginService {
//TODO: move to a better place. //TODO: move to a better place.
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
@ -108,6 +112,13 @@ public class BasePluginService implements PluginService {
return getData(pluginDao.findById(pluginId)); return getData(pluginDao.findById(pluginId));
} }
@Override
public ListenableFuture<PluginMetaData> findPluginByIdAsync(PluginId pluginId) {
validateId(pluginId, "Incorrect plugin id for search plugin request.");
ListenableFuture<PluginMetaDataEntity> pluginEntity = pluginDao.findByIdAsync(pluginId.getId());
return Futures.transform(pluginEntity, (com.google.common.base.Function<? super PluginMetaDataEntity, ? extends PluginMetaData>) input -> getData(input));
}
@Override @Override
public PluginMetaData findPluginByApiToken(String apiToken) { public PluginMetaData findPluginByApiToken(String apiToken) {
Validator.validateString(apiToken, "Incorrect plugin apiToken for search request."); Validator.validateString(apiToken, "Incorrect plugin apiToken for search request.");
@ -205,6 +216,7 @@ public class BasePluginService implements PluginService {
@Override @Override
public void deletePluginById(PluginId pluginId) { public void deletePluginById(PluginId pluginId) {
Validator.validateId(pluginId, "Incorrect plugin id for delete request."); Validator.validateId(pluginId, "Incorrect plugin id for delete request.");
deleteEntityRelations(pluginId);
checkRulesAndDelete(pluginId.getId()); checkRulesAndDelete(pluginId.getId());
} }

3
dao/src/main/java/org/thingsboard/server/dao/plugin/PluginService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.plugin; package org.thingsboard.server.dao.plugin;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
@ -29,6 +30,8 @@ public interface PluginService {
PluginMetaData findPluginById(PluginId pluginId); PluginMetaData findPluginById(PluginId pluginId);
ListenableFuture<PluginMetaData> findPluginByIdAsync(PluginId pluginId);
PluginMetaData findPluginByApiToken(String apiToken); PluginMetaData findPluginByApiToken(String apiToken);
TextPageData<PluginMetaData> findSystemPlugins(TextPageLink pageLink); TextPageData<PluginMetaData> findSystemPlugins(TextPageLink pageLink);

279
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java

@ -0,0 +1,279 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import com.datastax.driver.core.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.AbstractAsyncDao;
import org.thingsboard.server.dao.model.ModelConstants;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* Created by ashvayka on 25.04.17.
*/
@Component
@Slf4j
public class BaseRelationDao extends AbstractAsyncDao implements RelationDao {
private static final String SELECT_COLUMNS = "SELECT " +
ModelConstants.RELATION_FROM_ID_PROPERTY + "," +
ModelConstants.RELATION_FROM_TYPE_PROPERTY + "," +
ModelConstants.RELATION_TO_ID_PROPERTY + "," +
ModelConstants.RELATION_TO_TYPE_PROPERTY + "," +
ModelConstants.RELATION_TYPE_PROPERTY + "," +
ModelConstants.ADDITIONAL_INFO_PROPERTY;
public static final String FROM = " FROM ";
public static final String WHERE = " WHERE ";
public static final String AND = " AND ";
private PreparedStatement saveStmt;
private PreparedStatement findAllByFromStmt;
private PreparedStatement findAllByFromAndTypeStmt;
private PreparedStatement findAllByToStmt;
private PreparedStatement findAllByToAndTypeStmt;
private PreparedStatement checkRelationStmt;
private PreparedStatement deleteStmt;
private PreparedStatement deleteAllByEntityStmt;
@PostConstruct
public void init() {
super.startExecutor();
}
@Override
public ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from) {
BoundStatement stmt = getFindAllByFromStmt().bind().setUUID(0, from.getId()).setString(1, from.getEntityType().name());
return executeAsyncRead(from, stmt);
}
@Override
public ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType) {
BoundStatement stmt = getFindAllByFromAndTypeStmt().bind()
.setUUID(0, from.getId())
.setString(1, from.getEntityType().name())
.setString(2, relationType);
return executeAsyncRead(from, stmt);
}
@Override
public ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to) {
BoundStatement stmt = getFindAllByToStmt().bind().setUUID(0, to.getId()).setString(1, to.getEntityType().name());
return executeAsyncRead(to, stmt);
}
@Override
public ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType) {
BoundStatement stmt = getFindAllByToAndTypeStmt().bind()
.setUUID(0, to.getId())
.setString(1, to.getEntityType().name())
.setString(2, relationType);
return executeAsyncRead(to, stmt);
}
@Override
public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
BoundStatement stmt = getCheckRelationStmt().bind()
.setUUID(0, from.getId())
.setString(1, from.getEntityType().name())
.setUUID(2, to.getId())
.setString(3, to.getEntityType().name())
.setString(4, relationType);
return getFuture(executeAsyncRead(stmt), rs -> rs != null ? rs.one() != null : false);
}
@Override
public ListenableFuture<Boolean> saveRelation(EntityRelation relation) {
BoundStatement stmt = getSaveStmt().bind()
.setUUID(0, relation.getFrom().getId())
.setString(1, relation.getFrom().getEntityType().name())
.setUUID(2, relation.getTo().getId())
.setString(3, relation.getTo().getEntityType().name())
.setString(4, relation.getType())
.set(5, relation.getAdditionalInfo(), JsonNode.class);
ResultSetFuture future = executeAsyncWrite(stmt);
return getBooleanListenableFuture(future);
}
@Override
public ListenableFuture<Boolean> deleteRelation(EntityRelation relation) {
return deleteRelation(relation.getFrom(), relation.getTo(), relation.getType());
}
@Override
public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType) {
BoundStatement stmt = getDeleteStmt().bind()
.setUUID(0, from.getId())
.setString(1, from.getEntityType().name())
.setUUID(2, to.getId())
.setString(3, to.getEntityType().name())
.setString(4, relationType);
ResultSetFuture future = executeAsyncWrite(stmt);
return getBooleanListenableFuture(future);
}
@Override
public ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity) {
BoundStatement stmt = getDeleteAllByEntityStmt().bind()
.setUUID(0, entity.getId())
.setString(1, entity.getEntityType().name());
ResultSetFuture future = executeAsyncWrite(stmt);
return getBooleanListenableFuture(future);
}
private PreparedStatement getSaveStmt() {
if (saveStmt == null) {
saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
"(" + ModelConstants.RELATION_FROM_ID_PROPERTY +
"," + ModelConstants.RELATION_FROM_TYPE_PROPERTY +
"," + ModelConstants.RELATION_TO_ID_PROPERTY +
"," + ModelConstants.RELATION_TO_TYPE_PROPERTY +
"," + ModelConstants.RELATION_TYPE_PROPERTY +
"," + ModelConstants.ADDITIONAL_INFO_PROPERTY + ")" +
" VALUES(?, ?, ?, ?, ?, ?)");
}
return saveStmt;
}
private PreparedStatement getDeleteStmt() {
if (deleteStmt == null) {
deleteStmt = getSession().prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ?");
}
return deleteStmt;
}
private PreparedStatement getDeleteAllByEntityStmt() {
if (deleteAllByEntityStmt == null) {
deleteAllByEntityStmt = getSession().prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?");
}
return deleteAllByEntityStmt;
}
private PreparedStatement getFindAllByFromStmt() {
if (findAllByFromStmt == null) {
findAllByFromStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? ");
}
return findAllByFromStmt;
}
private PreparedStatement getFindAllByFromAndTypeStmt() {
if (findAllByFromAndTypeStmt == null) {
findAllByFromAndTypeStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? ");
}
return findAllByFromAndTypeStmt;
}
private PreparedStatement getFindAllByToStmt() {
if (findAllByToStmt == null) {
findAllByToStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " +
WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? ");
}
return findAllByToStmt;
}
private PreparedStatement getFindAllByToAndTypeStmt() {
if (findAllByToAndTypeStmt == null) {
findAllByToAndTypeStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " +
WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? ");
}
return findAllByToAndTypeStmt;
}
private PreparedStatement getCheckRelationStmt() {
if (checkRelationStmt == null) {
checkRelationStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? ");
}
return checkRelationStmt;
}
private EntityRelation getEntityRelation(Row row) {
EntityRelation relation = new EntityRelation();
relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY));
relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class));
relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY));
relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY));
return relation;
}
private EntityId toEntity(Row row, String uuidColumn, String typeColumn) {
return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn));
}
private ListenableFuture<List<EntityRelation>> executeAsyncRead(EntityId from, BoundStatement stmt) {
log.debug("Generated query [{}] for entity {}", stmt, from);
return getFuture(executeAsyncRead(stmt), rs -> {
List<Row> rows = rs.all();
List<EntityRelation> entries = new ArrayList<>(rows.size());
if (!rows.isEmpty()) {
rows.forEach(row -> {
entries.add(getEntityRelation(row));
});
}
return entries;
});
}
private ListenableFuture<Boolean> getBooleanListenableFuture(ResultSetFuture rsFuture) {
return getFuture(rsFuture, rs -> rs != null ? rs.wasApplied() : false);
}
private <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) {
return Futures.transform(future, new Function<ResultSet, T>() {
@Nullable
@Override
public T apply(@Nullable ResultSet input) {
return transformer.apply(input);
}
}, readResultsProcessingExecutor);
}
}

257
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -0,0 +1,257 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.exception.DataValidationException;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by ashvayka on 28.04.17.
*/
@Service
@Slf4j
public class BaseRelationService implements RelationService {
@Autowired
private RelationDao relationDao;
@Override
public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
log.trace("Executing checkRelation [{}][{}][{}]", from, to, relationType);
validate(from, to, relationType);
return relationDao.checkRelation(from, to, relationType);
}
@Override
public ListenableFuture<Boolean> saveRelation(EntityRelation relation) {
log.trace("Executing saveRelation [{}]", relation);
validate(relation);
return relationDao.saveRelation(relation);
}
@Override
public ListenableFuture<Boolean> deleteRelation(EntityRelation relation) {
log.trace("Executing deleteRelation [{}]", relation);
validate(relation);
return relationDao.deleteRelation(relation);
}
@Override
public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType) {
log.trace("Executing deleteRelation [{}][{}][{}]", from, to, relationType);
validate(from, to, relationType);
return relationDao.deleteRelation(from, to, relationType);
}
@Override
public ListenableFuture<Boolean> deleteEntityRelations(EntityId entity) {
log.trace("Executing deleteEntityRelations [{}]", entity);
validate(entity);
ListenableFuture<List<EntityRelation>> inboundRelations = relationDao.findAllByTo(entity);
ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, new AsyncFunction<List<EntityRelation>, List<Boolean>>() {
@Override
public ListenableFuture<List<Boolean>> apply(List<EntityRelation> relations) throws Exception {
List<ListenableFuture<Boolean>> results = new ArrayList<>();
for (EntityRelation relation : relations) {
results.add(relationDao.deleteRelation(relation));
}
return Futures.allAsList(results);
}
});
ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
ListenableFuture<Boolean> outboundFuture = relationDao.deleteOutboundRelations(entity);
return Futures.transform(Futures.allAsList(Arrays.asList(inboundFuture, outboundFuture)), getListToBooleanFunction());
}
@Override
public ListenableFuture<List<EntityRelation>> findByFrom(EntityId from) {
log.trace("Executing findByFrom [{}]", from);
validate(from);
return relationDao.findAllByFrom(from);
}
@Override
public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
log.trace("Executing findByFromAndType [{}][{}]", from, relationType);
validate(from);
validateType(relationType);
return relationDao.findAllByFromAndType(from, relationType);
}
@Override
public ListenableFuture<List<EntityRelation>> findByTo(EntityId to) {
log.trace("Executing findByTo [{}]", to);
validate(to);
return relationDao.findAllByTo(to);
}
@Override
public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType) {
log.trace("Executing findByToAndType [{}][{}]", to, relationType);
validate(to);
validateType(relationType);
return relationDao.findAllByToAndType(to, relationType);
}
@Override
public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
log.trace("Executing findByQuery [{}][{}]", query);
RelationsSearchParameters params = query.getParameters();
final List<EntityTypeFilter> filters = query.getFilters();
if (filters == null || filters.isEmpty()) {
log.warn("Failed to query relations. Filters are not set [{}]", query);
throw new RuntimeException("Filters are not set!");
}
int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE;
try {
ListenableFuture<Set<EntityRelation>> relationSet = findRelationsRecursively(params.getEntityId(), params.getDirection(), maxLvl, new ConcurrentHashMap<>());
return Futures.transform(relationSet, (Function<Set<EntityRelation>, List<EntityRelation>>) input -> {
List<EntityRelation> relations = new ArrayList<>();
for (EntityRelation relation : input) {
for (EntityTypeFilter filter : filters) {
if (match(filter, relation, params.getDirection())) {
relations.add(relation);
break;
}
}
}
return relations;
});
} catch (Exception e) {
log.warn("Failed to query relations: [{}]", query, e);
throw new RuntimeException(e);
}
}
protected void validate(EntityRelation relation) {
if (relation == null) {
throw new DataValidationException("Relation type should be specified!");
}
validate(relation.getFrom(), relation.getTo(), relation.getType());
}
protected void validate(EntityId from, EntityId to, String type) {
validateType(type);
if (from == null) {
throw new DataValidationException("Relation should contain from entity!");
}
if (to == null) {
throw new DataValidationException("Relation should contain to entity!");
}
}
private void validateType(String type) {
if (StringUtils.isEmpty(type)) {
throw new DataValidationException("Relation type should be specified!");
}
}
protected void validate(EntityId entity) {
if (entity == null) {
throw new DataValidationException("Entity should be specified!");
}
}
private Function<List<Boolean>, Boolean> getListToBooleanFunction() {
return new Function<List<Boolean>, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable List<Boolean> results) {
for (Boolean result : results) {
if (result == null || !result) {
return false;
}
}
return true;
}
};
}
private boolean match(EntityTypeFilter filter, EntityRelation relation, EntitySearchDirection direction) {
if (StringUtils.isEmpty(filter.getRelationType()) || filter.getRelationType().equals(relation.getType())) {
if (filter.getEntityTypes() == null || filter.getEntityTypes().isEmpty()) {
return true;
} else {
EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom();
return filter.getEntityTypes().contains(entityId.getEntityType());
}
} else {
return false;
}
}
private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction, int lvl, final ConcurrentHashMap<EntityId, Boolean> uniqueMap) throws Exception {
if (lvl == 0) {
return Futures.immediateFuture(Collections.emptySet());
}
lvl--;
//TODO: try to remove this blocking operation
Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction).get());
Set<EntityId> childrenIds = new HashSet<>();
for (EntityRelation childRelation : children) {
log.info("Found Relation: {}", childRelation);
EntityId childId;
if (direction == EntitySearchDirection.FROM) {
childId = childRelation.getTo();
} else {
childId = childRelation.getFrom();
}
if (uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) {
log.info("Adding Relation: {}", childId);
if (childrenIds.add(childId)) {
log.info("Added Relation: {}", childId);
}
}
}
List<ListenableFuture<Set<EntityRelation>>> futures = new ArrayList<>();
for (EntityId entityId : childrenIds) {
futures.add(findRelationsRecursively(entityId, direction, lvl, uniqueMap));
}
//TODO: try to remove this blocking operation
List<Set<EntityRelation>> relations = Futures.successfulAsList(futures).get();
relations.forEach(r -> r.forEach(d -> children.add(d)));
return Futures.immediateFuture(children);
}
private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) {
ListenableFuture<List<EntityRelation>> relations;
if (direction == EntitySearchDirection.FROM) {
relations = findByFrom(rootId);
} else {
relations = findByTo(rootId);
}
return relations;
}
}

31
dao/src/main/java/org/thingsboard/server/dao/relation/EntityRelationsQuery.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import lombok.Data;
import java.util.List;
/**
* Created by ashvayka on 02.05.17.
*/
@Data
public class EntityRelationsQuery {
private RelationsSearchParameters parameters;
private List<EntityTypeFilter> filters;
}

25
dao/src/main/java/org/thingsboard/server/dao/relation/EntitySearchDirection.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2017 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.dao.relation;
/**
* Created by ashvayka on 02.05.17.
*/
public enum EntitySearchDirection {
FROM, TO;
}

35
dao/src/main/java/org/thingsboard/server/dao/relation/EntityTypeFilter.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import javax.annotation.Nullable;
import java.util.List;
/**
* Created by ashvayka on 02.05.17.
*/
@Data
@AllArgsConstructor
public class EntityTypeFilter {
@Nullable
private String relationType;
@Nullable
private List<EntityType> entityTypes;
}

47
dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import java.util.List;
/**
* Created by ashvayka on 25.04.17.
*/
public interface RelationDao {
ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from);
ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType);
ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to);
ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType);
ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType);
ListenableFuture<Boolean> saveRelation(EntityRelation relation);
ListenableFuture<Boolean> deleteRelation(EntityRelation relation);
ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType);
ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity);
}

49
dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import java.util.List;
/**
* Created by ashvayka on 27.04.17.
*/
public interface RelationService {
ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType);
ListenableFuture<Boolean> saveRelation(EntityRelation relation);
ListenableFuture<Boolean> deleteRelation(EntityRelation relation);
ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType);
ListenableFuture<Boolean> deleteEntityRelations(EntityId entity);
ListenableFuture<List<EntityRelation>> findByFrom(EntityId from);
ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType);
ListenableFuture<List<EntityRelation>> findByTo(EntityId to);
ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType);
ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
}

48
dao/src/main/java/org/thingsboard/server/dao/relation/RelationsSearchParameters.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2017 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.dao.relation;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import java.util.UUID;
/**
* Created by ashvayka on 03.05.17.
*/
@Data
@AllArgsConstructor
public class RelationsSearchParameters {
private UUID rootId;
private EntityType rootType;
private EntitySearchDirection direction;
private int maxLevel = 1;
public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel) {
this.rootId = entityId.getId();
this.rootType = entityId.getEntityType();
this.direction = direction;
this.maxLevel = maxLevel;
}
public EntityId getEntityId() {
return EntityIdFactory.getByTypeAndUuid(rootType, rootId);
}
}

15
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java

@ -17,10 +17,13 @@ package org.thingsboard.server.dao.rule;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
@ -31,9 +34,11 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData; import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService; import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException; import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.AssetEntity;
import org.thingsboard.server.dao.model.RuleMetaDataEntity; import org.thingsboard.server.dao.model.RuleMetaDataEntity;
import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
@ -53,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service @Service
@Slf4j @Slf4j
public class BaseRuleService implements RuleService { public class BaseRuleService extends BaseEntityService implements RuleService {
private final TenantId systemTenantId = new TenantId(NULL_UUID); private final TenantId systemTenantId = new TenantId(NULL_UUID);
@ -166,6 +171,13 @@ public class BaseRuleService implements RuleService {
return getData(ruleDao.findById(ruleId.getId())); return getData(ruleDao.findById(ruleId.getId()));
} }
@Override
public ListenableFuture<RuleMetaData> findRuleByIdAsync(RuleId ruleId) {
validateId(ruleId, "Incorrect rule id for search rule request.");
ListenableFuture<RuleMetaDataEntity> ruleEntity = ruleDao.findByIdAsync(ruleId.getId());
return Futures.transform(ruleEntity, (com.google.common.base.Function<? super RuleMetaDataEntity, ? extends RuleMetaData>) input -> getData(input));
}
@Override @Override
public List<RuleMetaData> findPluginRules(String pluginToken) { public List<RuleMetaData> findPluginRules(String pluginToken) {
List<RuleMetaDataEntity> ruleEntities = ruleDao.findRulesByPlugin(pluginToken); List<RuleMetaDataEntity> ruleEntities = ruleDao.findRulesByPlugin(pluginToken);
@ -235,6 +247,7 @@ public class BaseRuleService implements RuleService {
@Override @Override
public void deleteRuleById(RuleId ruleId) { public void deleteRuleById(RuleId ruleId) {
validateId(ruleId, "Incorrect rule id for delete rule request."); validateId(ruleId, "Incorrect rule id for delete rule request.");
deleteEntityRelations(ruleId);
ruleDao.deleteById(ruleId); ruleDao.deleteById(ruleId);
} }

3
dao/src/main/java/org/thingsboard/server/dao/rule/RuleService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.rule; package org.thingsboard.server.dao.rule;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
@ -29,6 +30,8 @@ public interface RuleService {
RuleMetaData findRuleById(RuleId ruleId); RuleMetaData findRuleById(RuleId ruleId);
ListenableFuture<RuleMetaData> findRuleByIdAsync(RuleId ruleId);
List<RuleMetaData> findPluginRules(String pluginToken); List<RuleMetaData> findPluginRules(String pluginToken);
TextPageData<RuleMetaData> findSystemRules(TextPageLink pageLink); TextPageData<RuleMetaData> findSystemRules(TextPageLink pageLink);

14
dao/src/main/java/org/thingsboard/server/dao/service/Validator.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -24,6 +25,19 @@ import java.util.UUID;
public class Validator { public class Validator {
/**
* This method validate <code>EntityId</code> entity id. If entity id is invalid than throw
* <code>IncorrectParameterException</code> exception
*
* @param entityId the entityId
* @param errorMessage the error message for exception
*/
public static void validateEntityId(EntityId entityId, String errorMessage) {
if (entityId == null || entityId.getId() == null) {
throw new IncorrectParameterException(errorMessage);
}
}
/** /**
* This method validate <code>String</code> string. If string is invalid than throw * This method validate <code>String</code> string. If string is invalid than throw
* <code>IncorrectParameterException</code> exception * <code>IncorrectParameterException</code> exception

15
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.tenant; package org.thingsboard.server.dao.tenant;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
@ -22,16 +23,16 @@ import org.thingsboard.server.common.data.page.TextPageLink;
public interface TenantService { public interface TenantService {
public Tenant findTenantById(TenantId tenantId); Tenant findTenantById(TenantId tenantId);
public Tenant saveTenant(Tenant tenant); ListenableFuture<Tenant> findTenantByIdAsync(TenantId customerId);
public void deleteTenant(TenantId tenantId); Tenant saveTenant(Tenant tenant);
public TextPageData<Tenant> findTenants(TextPageLink pageLink); void deleteTenant(TenantId tenantId);
//public TextPageData<Tenant> findTenantsByTitle(String title, PageLink pageLink); TextPageData<Tenant> findTenants(TextPageLink pageLink);
public void deleteTenants(); void deleteTenants();
} }

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

@ -17,11 +17,16 @@ package org.thingsboard.server.dao.tenant;
import static org.thingsboard.server.dao.DaoUtil.convertDataList; import static org.thingsboard.server.dao.DaoUtil.convertDataList;
import static org.thingsboard.server.dao.DaoUtil.getData; import static org.thingsboard.server.dao.DaoUtil.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
import java.util.List; import java.util.List;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
@ -29,15 +34,15 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.TenantEntity; import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
@ -45,19 +50,19 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
@Service @Service
@Slf4j @Slf4j
public class TenantServiceImpl implements TenantService { public class TenantServiceImpl extends BaseEntityService implements TenantService {
private static final String DEFAULT_TENANT_REGION = "Global"; private static final String DEFAULT_TENANT_REGION = "Global";
@Autowired @Autowired
private TenantDao tenantDao; private TenantDao tenantDao;
@Autowired @Autowired
private UserService userService; private UserService userService;
@Autowired @Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@ -72,7 +77,7 @@ public class TenantServiceImpl implements TenantService {
@Autowired @Autowired
private PluginService pluginService; private PluginService pluginService;
@Override @Override
public Tenant findTenantById(TenantId tenantId) { public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId); log.trace("Executing findTenantById [{}]", tenantId);
@ -81,6 +86,14 @@ public class TenantServiceImpl implements TenantService {
return getData(tenantEntity); return getData(tenantEntity);
} }
@Override
public ListenableFuture<Tenant> findTenantByIdAsync(TenantId tenantId) {
log.trace("Executing TenantIdAsync [{}]", tenantId);
validateId(tenantId, "Incorrect tenantId " + tenantId);
ListenableFuture<TenantEntity> tenantEntity = tenantDao.findByIdAsync(tenantId.getId());
return Futures.transform(tenantEntity, (Function<? super TenantEntity, ? extends Tenant>) input -> getData(input));
}
@Override @Override
public Tenant saveTenant(Tenant tenant) { public Tenant saveTenant(Tenant tenant) {
log.trace("Executing saveTenant [{}]", tenant); log.trace("Executing saveTenant [{}]", tenant);
@ -102,6 +115,7 @@ public class TenantServiceImpl implements TenantService {
ruleService.deleteRulesByTenantId(tenantId); ruleService.deleteRulesByTenantId(tenantId);
pluginService.deletePluginsByTenantId(tenantId); pluginService.deletePluginsByTenantId(tenantId);
tenantDao.removeById(tenantId.getId()); tenantDao.removeById(tenantId.getId());
deleteEntityRelations(tenantId);
} }
@Override @Override
@ -131,10 +145,10 @@ public class TenantServiceImpl implements TenantService {
} }
} }
}; };
private PaginatedRemover<String, TenantEntity> tenantsRemover = private PaginatedRemover<String, TenantEntity> tenantsRemover =
new PaginatedRemover<String, TenantEntity>() { new PaginatedRemover<String, TenantEntity>() {
@Override @Override
protected List<TenantEntity> findEntities(String region, TextPageLink pageLink) { protected List<TenantEntity> findEntities(String region, TextPageLink pageLink) {
return tenantDao.findTenantsByRegion(region, pageLink); return tenantDao.findTenantsByRegion(region, pageLink);

77
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesDao.java

@ -26,6 +26,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.dao.AbstractAsyncDao; import org.thingsboard.server.dao.AbstractAsyncDao;
@ -94,8 +95,8 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
@Override @Override
public ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries) { public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityType, entityId, query)).collect(Collectors.toList()); List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityId, query)).collect(Collectors.toList());
return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() { return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
@Nullable @Nullable
@Override @Override
@ -108,9 +109,9 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
private ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query) { private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) { if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(entityType, entityId, query); return findAllAsyncWithLimit(entityId, query);
} else { } else {
long step = Math.max(query.getInterval(), minAggregationStepMs); long step = Math.max(query.getInterval(), minAggregationStepMs);
long stepTs = query.getStartTs(); long stepTs = query.getStartTs();
@ -119,7 +120,7 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
long startTs = stepTs; long startTs = stepTs;
long endTs = stepTs + step; long endTs = stepTs + step;
TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation()); TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation());
futures.add(findAndAggregateAsync(entityType, entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs))); futures.add(findAndAggregateAsync(entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs)));
stepTs = endTs; stepTs = endTs;
} }
ListenableFuture<List<Optional<TsKvEntry>>> future = Futures.allAsList(futures); ListenableFuture<List<Optional<TsKvEntry>>> future = Futures.allAsList(futures);
@ -133,11 +134,11 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
} }
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(String entityType, UUID entityId, TsKvQuery query) { private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
long minPartition = toPartitionTs(query.getStartTs()); long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs()); long maxPartition = toPartitionTs(query.getEndTs());
ResultSetFuture partitionsFuture = fetchPartitions(entityType, entityId, query.getKey(), minPartition, maxPartition); ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
final SimpleListenableFuture<List<TsKvEntry>> resultFuture = new SimpleListenableFuture<>(); final SimpleListenableFuture<List<TsKvEntry>> resultFuture = new SimpleListenableFuture<>();
final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
@ -145,13 +146,13 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() { Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
@Override @Override
public void onSuccess(@Nullable List<Long> partitions) { public void onSuccess(@Nullable List<Long> partitions) {
TsKvQueryCursor cursor = new TsKvQueryCursor(entityType, entityId, query, partitions); TsKvQueryCursor cursor = new TsKvQueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitions);
findAllAsyncSequentiallyWithLimit(cursor, resultFuture); findAllAsyncSequentiallyWithLimit(cursor, resultFuture);
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityType, entityId, minPartition, maxPartition, t); log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
} }
}, readResultsProcessingExecutor); }, readResultsProcessingExecutor);
@ -187,19 +188,19 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
} }
private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(String entityType, UUID entityId, TsKvQuery query, long minPartition, long maxPartition) { private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, TsKvQuery query, long minPartition, long maxPartition) {
final Aggregation aggregation = query.getAggregation(); final Aggregation aggregation = query.getAggregation();
final String key = query.getKey(); final String key = query.getKey();
final long startTs = query.getStartTs(); final long startTs = query.getStartTs();
final long endTs = query.getEndTs(); final long endTs = query.getEndTs();
final long ts = startTs + (endTs - startTs) / 2; final long ts = startTs + (endTs - startTs) / 2;
ResultSetFuture partitionsFuture = fetchPartitions(entityType, entityId, key, minPartition, maxPartition); ResultSetFuture partitionsFuture = fetchPartitions(entityId, key, minPartition, maxPartition);
ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transform(partitionsListFuture, ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transform(partitionsListFuture,
getFetchChunksAsyncFunction(entityType, entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor); getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor);
return Futures.transform(aggregationChunks, new AggregatePartitionsFunction(aggregation, key, ts), readResultsProcessingExecutor); return Futures.transform(aggregationChunks, new AggregatePartitionsFunction(aggregation, key, ts), readResultsProcessingExecutor);
} }
@ -209,21 +210,21 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
.map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)).collect(Collectors.toList()); .map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)).collect(Collectors.toList());
} }
private AsyncFunction<List<Long>, List<ResultSet>> getFetchChunksAsyncFunction(String entityType, UUID entityId, String key, Aggregation aggregation, long startTs, long endTs) { private AsyncFunction<List<Long>, List<ResultSet>> getFetchChunksAsyncFunction(EntityId entityId, String key, Aggregation aggregation, long startTs, long endTs) {
return partitions -> { return partitions -> {
try { try {
PreparedStatement proto = getFetchStmt(aggregation); PreparedStatement proto = getFetchStmt(aggregation);
List<ResultSetFuture> futures = new ArrayList<>(partitions.size()); List<ResultSetFuture> futures = new ArrayList<>(partitions.size());
for (Long partition : partitions) { for (Long partition : partitions) {
log.trace("Fetching data for partition [{}] for entityType {} and entityId {}", partition, entityType, entityId); log.trace("Fetching data for partition [{}] for entityType {} and entityId {}", partition, entityId.getEntityType(), entityId.getId());
BoundStatement stmt = proto.bind(); BoundStatement stmt = proto.bind();
stmt.setString(0, entityType); stmt.setString(0, entityId.getEntityType().name());
stmt.setUUID(1, entityId); stmt.setUUID(1, entityId.getId());
stmt.setString(2, key); stmt.setString(2, key);
stmt.setLong(3, partition); stmt.setLong(3, partition);
stmt.setLong(4, startTs); stmt.setLong(4, startTs);
stmt.setLong(5, endTs); stmt.setLong(5, endTs);
log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityType, entityId); log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityId.getEntityType(), entityId.getId());
futures.add(executeAsyncRead(stmt)); futures.add(executeAsyncRead(stmt));
} }
return Futures.allAsList(futures); return Futures.allAsList(futures);
@ -235,30 +236,30 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
@Override @Override
public ResultSetFuture findLatest(String entityType, UUID entityId, String key) { public ResultSetFuture findLatest(EntityId entityId, String key) {
BoundStatement stmt = getFindLatestStmt().bind(); BoundStatement stmt = getFindLatestStmt().bind();
stmt.setString(0, entityType); stmt.setString(0, entityId.getEntityType().name());
stmt.setUUID(1, entityId); stmt.setUUID(1, entityId.getId());
stmt.setString(2, key); stmt.setString(2, key);
log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityType, entityId); log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityId.getEntityType(), entityId.getId());
return executeAsyncRead(stmt); return executeAsyncRead(stmt);
} }
@Override @Override
public ResultSetFuture findAllLatest(String entityType, UUID entityId) { public ResultSetFuture findAllLatest(EntityId entityId) {
BoundStatement stmt = getFindAllLatestStmt().bind(); BoundStatement stmt = getFindAllLatestStmt().bind();
stmt.setString(0, entityType); stmt.setString(0, entityId.getEntityType().name());
stmt.setUUID(1, entityId); stmt.setUUID(1, entityId.getId());
log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityType, entityId); log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityId.getEntityType(), entityId.getId());
return executeAsyncRead(stmt); return executeAsyncRead(stmt);
} }
@Override @Override
public ResultSetFuture save(String entityType, UUID entityId, long partition, TsKvEntry tsKvEntry) { public ResultSetFuture save(EntityId entityId, long partition, TsKvEntry tsKvEntry) {
DataType type = tsKvEntry.getDataType(); DataType type = tsKvEntry.getDataType();
BoundStatement stmt = getSaveStmt(type).bind() BoundStatement stmt = getSaveStmt(type).bind()
.setString(0, entityType) .setString(0, entityId.getEntityType().name())
.setUUID(1, entityId) .setUUID(1, entityId.getId())
.setString(2, tsKvEntry.getKey()) .setString(2, tsKvEntry.getKey())
.setLong(3, partition) .setLong(3, partition)
.setLong(4, tsKvEntry.getTs()); .setLong(4, tsKvEntry.getTs());
@ -267,11 +268,11 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
@Override @Override
public ResultSetFuture saveLatest(String entityType, UUID entityId, TsKvEntry tsKvEntry) { public ResultSetFuture saveLatest(EntityId entityId, TsKvEntry tsKvEntry) {
DataType type = tsKvEntry.getDataType(); DataType type = tsKvEntry.getDataType();
BoundStatement stmt = getLatestStmt(type).bind() BoundStatement stmt = getLatestStmt(type).bind()
.setString(0, entityType) .setString(0, entityId.getEntityType().name())
.setUUID(1, entityId) .setUUID(1, entityId.getId())
.setString(2, tsKvEntry.getKey()) .setString(2, tsKvEntry.getKey())
.setLong(3, tsKvEntry.getTs()); .setLong(3, tsKvEntry.getTs());
addValue(tsKvEntry, stmt, 4); addValue(tsKvEntry, stmt, 4);
@ -279,11 +280,11 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
} }
@Override @Override
public ResultSetFuture savePartition(String entityType, UUID entityId, long partition, String key) { public ResultSetFuture savePartition(EntityId entityId, long partition, String key) {
log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityType, entityId, key); log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityId.getEntityType(), entityId.getId(), key);
return executeAsyncWrite(getPartitionInsertStmt().bind() return executeAsyncWrite(getPartitionInsertStmt().bind()
.setString(0, entityType) .setString(0, entityId.getEntityType().name())
.setUUID(1, entityId) .setUUID(1, entityId.getId())
.setLong(2, partition) .setLong(2, partition)
.setString(3, key)); .setString(3, key));
} }
@ -339,9 +340,9 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
* Select existing partitions from the table * Select existing partitions from the table
* <code>{@link ModelConstants#TS_KV_PARTITIONS_CF}</code> for the given entity * <code>{@link ModelConstants#TS_KV_PARTITIONS_CF}</code> for the given entity
*/ */
private ResultSetFuture fetchPartitions(String entityType, UUID entityId, String key, long minPartition, long maxPartition) { private ResultSetFuture fetchPartitions(EntityId entityId, String key, long minPartition, long maxPartition) {
Select.Where select = QueryBuilder.select(ModelConstants.PARTITION_COLUMN).from(ModelConstants.TS_KV_PARTITIONS_CF).where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityType)) Select.Where select = QueryBuilder.select(ModelConstants.PARTITION_COLUMN).from(ModelConstants.TS_KV_PARTITIONS_CF).where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityId.getEntityType().name()))
.and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId)).and(eq(ModelConstants.KEY_COLUMN, key)); .and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId.getId())).and(eq(ModelConstants.KEY_COLUMN, key));
select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition)); select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition));
select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition)); select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition));
return executeAsyncRead(select); return executeAsyncRead(select);

44
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
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 lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery; import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -59,30 +60,30 @@ public class BaseTimeseriesService implements TimeseriesService {
private TimeseriesDao timeseriesDao; private TimeseriesDao timeseriesDao;
@Override @Override
public ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries) { public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries) {
validate(entityType, entityId); validate(entityId);
queries.forEach(query -> validate(query)); queries.forEach(query -> validate(query));
return timeseriesDao.findAllAsync(entityType, entityId.getId(), queries); return timeseriesDao.findAllAsync(entityId, queries);
} }
@Override @Override
public ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys) { public ListenableFuture<List<ResultSet>> findLatest(EntityId entityId, Collection<String> keys) {
validate(entityType, entityId); validate(entityId);
List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(keys.size()); List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(keys.size());
keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key)); keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key));
keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityType, entityId.getId(), key))); keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
return Futures.allAsList(futures); return Futures.allAsList(futures);
} }
@Override @Override
public ResultSetFuture findAllLatest(String entityType, UUIDBased entityId) { public ResultSetFuture findAllLatest(EntityId entityId) {
validate(entityType, entityId); validate(entityId);
return timeseriesDao.findAllLatest(entityType, entityId.getId()); return timeseriesDao.findAllLatest(entityId);
} }
@Override @Override
public ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, TsKvEntry tsKvEntry) { public ListenableFuture<List<ResultSet>> save(EntityId entityId, TsKvEntry tsKvEntry) {
validate(entityType, entityId); validate(entityId);
if (tsKvEntry == null) { if (tsKvEntry == null) {
throw new IncorrectParameterException("Key value entry can't be null"); throw new IncorrectParameterException("Key value entry can't be null");
} }
@ -90,13 +91,13 @@ public class BaseTimeseriesService implements TimeseriesService {
long partitionTs = timeseriesDao.toPartitionTs(tsKvEntry.getTs()); long partitionTs = timeseriesDao.toPartitionTs(tsKvEntry.getTs());
List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY);
saveAndRegisterFutures(futures, entityType, tsKvEntry, uid, partitionTs); saveAndRegisterFutures(futures, entityId, tsKvEntry, partitionTs);
return Futures.allAsList(futures); return Futures.allAsList(futures);
} }
@Override @Override
public ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, List<TsKvEntry> tsKvEntries) { public ListenableFuture<List<ResultSet>> save(EntityId entityId, List<TsKvEntry> tsKvEntries) {
validate(entityType, entityId); validate(entityId);
List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY);
for (TsKvEntry tsKvEntry : tsKvEntries) { for (TsKvEntry tsKvEntry : tsKvEntries) {
if (tsKvEntry == null) { if (tsKvEntry == null) {
@ -104,7 +105,7 @@ public class BaseTimeseriesService implements TimeseriesService {
} }
UUID uid = entityId.getId(); UUID uid = entityId.getId();
long partitionTs = timeseriesDao.toPartitionTs(tsKvEntry.getTs()); long partitionTs = timeseriesDao.toPartitionTs(tsKvEntry.getTs());
saveAndRegisterFutures(futures, entityType, tsKvEntry, uid, partitionTs); saveAndRegisterFutures(futures, entityId, tsKvEntry, partitionTs);
} }
return Futures.allAsList(futures); return Futures.allAsList(futures);
} }
@ -119,15 +120,14 @@ public class BaseTimeseriesService implements TimeseriesService {
return timeseriesDao.convertResultToTsKvEntryList(rs.all()); return timeseriesDao.convertResultToTsKvEntryList(rs.all());
} }
private void saveAndRegisterFutures(List<ResultSetFuture> futures, String entityType, TsKvEntry tsKvEntry, UUID uid, long partitionTs) { private void saveAndRegisterFutures(List<ResultSetFuture> futures, EntityId entityId, TsKvEntry tsKvEntry, long partitionTs) {
futures.add(timeseriesDao.savePartition(entityType, uid, partitionTs, tsKvEntry.getKey())); futures.add(timeseriesDao.savePartition(entityId, partitionTs, tsKvEntry.getKey()));
futures.add(timeseriesDao.saveLatest(entityType, uid, tsKvEntry)); futures.add(timeseriesDao.saveLatest(entityId, tsKvEntry));
futures.add(timeseriesDao.save(entityType, uid, partitionTs, tsKvEntry)); futures.add(timeseriesDao.save(entityId, partitionTs, tsKvEntry));
} }
private static void validate(String entityType, UUIDBased entityId) { private static void validate(EntityId entityId) {
Validator.validateString(entityType, "Incorrect entityType " + entityType); Validator.validateEntityId(entityId, "Incorrect entityId " + entityId);
Validator.validateId(entityId, "Incorrect entityId " + entityId);
} }
private static void validate(TsKvQuery query) { private static void validate(TsKvQuery query) {

13
dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.timeseries;
import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row; import com.datastax.driver.core.Row;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.common.data.kv.TsKvQuery;
@ -33,17 +34,17 @@ public interface TimeseriesDao {
long toPartitionTs(long ts); long toPartitionTs(long ts);
ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries); ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries);
ResultSetFuture findLatest(String entityType, UUID entityId, String key); ResultSetFuture findLatest(EntityId entityId, String key);
ResultSetFuture findAllLatest(String entityType, UUID entityId); ResultSetFuture findAllLatest(EntityId entityId);
ResultSetFuture save(String entityType, UUID entityId, long partition, TsKvEntry tsKvEntry); ResultSetFuture save(EntityId entityId, long partition, TsKvEntry tsKvEntry);
ResultSetFuture savePartition(String entityType, UUID entityId, long partition, String key); ResultSetFuture savePartition(EntityId entityId, long partition, String key);
ResultSetFuture saveLatest(String entityType, UUID entityId, TsKvEntry tsKvEntry); ResultSetFuture saveLatest(EntityId entityId, TsKvEntry tsKvEntry);
TsKvEntry convertResultToTsKvEntry(Row row); TsKvEntry convertResultToTsKvEntry(Row row);

11
dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java

@ -20,6 +20,7 @@ import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row; import com.datastax.driver.core.Row;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
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.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery; import org.thingsboard.server.common.data.kv.TsKvQuery;
@ -33,15 +34,15 @@ import java.util.Set;
*/ */
public interface TimeseriesService { public interface TimeseriesService {
ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries); ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries);
ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys); ListenableFuture<List<ResultSet>> findLatest(EntityId entityId, Collection<String> keys);
ResultSetFuture findAllLatest(String entityType, UUIDBased entityId); ResultSetFuture findAllLatest(EntityId entityId);
ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, TsKvEntry tsKvEntry); ListenableFuture<List<ResultSet>> save(EntityId entityId, TsKvEntry tsKvEntry);
ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, List<TsKvEntry> tsKvEntry); ListenableFuture<List<ResultSet>> save(EntityId entityId, List<TsKvEntry> tsKvEntry);
TsKvEntry convertResultToTsKvEntry(Row row); TsKvEntry convertResultToTsKvEntry(Row row);

6
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -35,20 +35,19 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.*; import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@Slf4j @Slf4j
public class UserServiceImpl implements UserService { public class UserServiceImpl extends BaseEntityService implements UserService {
@Autowired @Autowired
private UserDao userDao; private UserDao userDao;
@ -169,6 +168,7 @@ public class UserServiceImpl implements UserService {
validateId(userId, "Incorrect userId " + userId); validateId(userId, "Incorrect userId " + userId);
UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByUserId(userId.getId()); UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByUserId(userId.getId());
userCredentialsDao.removeById(userCredentialsEntity.getId()); userCredentialsDao.removeById(userCredentialsEntity.getId());
deleteEntityRelations(userId);
userDao.removeById(userId.getId()); userDao.removeById(userId.getId());
} }

51
dao/src/main/resources/schema.cql

@ -156,6 +156,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.device (
tenant_id timeuuid, tenant_id timeuuid,
customer_id timeuuid, customer_id timeuuid,
name text, name text,
type text,
search_text text, search_text text,
additional_info text, additional_info text,
PRIMARY KEY (id, tenant_id, customer_id) PRIMARY KEY (id, tenant_id, customer_id)
@ -202,6 +203,56 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_credent
WHERE credentials_id IS NOT NULL AND id IS NOT NULL WHERE credentials_id IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( credentials_id, id ); PRIMARY KEY ( credentials_id, id );
CREATE TABLE IF NOT EXISTS thingsboard.asset (
id timeuuid,
tenant_id timeuuid,
customer_id timeuuid,
name text,
type text,
search_text text,
additional_info text,
PRIMARY KEY (id, tenant_id, customer_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_name AS
SELECT *
from thingsboard.asset
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, name, id, customer_id)
WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_search_text AS
SELECT *
from thingsboard.asset
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id, customer_id)
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_and_search_text AS
SELECT *
from thingsboard.asset
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( customer_id, tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.relation (
from_id timeuuid,
from_type text,
to_id timeuuid,
to_type text,
relation_type text,
additional_info text,
PRIMARY KEY ((from_id, from_type), relation_type, to_id, to_type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS
SELECT *
from thingsboard.relation
WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL
PRIMARY KEY ((to_id, to_type), relation_type, from_id, from_type)
WITH CLUSTERING ORDER BY ( relation_type ASC, from_id ASC, from_type ASC);
CREATE TABLE IF NOT EXISTS thingsboard.widgets_bundle ( CREATE TABLE IF NOT EXISTS thingsboard.widgets_bundle (
id timeuuid, id timeuuid,
tenant_id timeuuid, tenant_id timeuuid,

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

@ -47,6 +47,7 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -110,6 +111,9 @@ public abstract class AbstractServiceTest {
@Autowired @Autowired
protected EventService eventService; protected EventService eventService;
@Autowired
protected RelationService relationService;
@Autowired @Autowired
private ComponentDescriptorService componentDescriptorService; private ComponentDescriptorService componentDescriptorService;

283
dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java

@ -0,0 +1,283 @@
/**
* Copyright © 2016-2017 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.dao.service;
import com.datastax.driver.core.utils.UUIDs;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.relation.EntityTypeFilter;
import org.thingsboard.server.dao.relation.RelationsSearchParameters;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class RelationServiceImplTest extends AbstractServiceTest {
@Before
public void before() {
}
@After
public void after() {
}
@Test
public void testSaveRelation() throws ExecutionException, InterruptedException {
AssetId parentId = new AssetId(UUIDs.timeBased());
AssetId childId = new AssetId(UUIDs.timeBased());
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(saveRelation(relation));
Assert.assertTrue(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get());
Assert.assertFalse(relationService.checkRelation(parentId, childId, "NOT_EXISTING_TYPE").get());
Assert.assertFalse(relationService.checkRelation(childId, parentId, EntityRelation.CONTAINS_TYPE).get());
Assert.assertFalse(relationService.checkRelation(childId, parentId, "NOT_EXISTING_TYPE").get());
}
@Test
public void testDeleteRelation() throws ExecutionException, InterruptedException {
AssetId parentId = new AssetId(UUIDs.timeBased());
AssetId childId = new AssetId(UUIDs.timeBased());
AssetId subChildId = new AssetId(UUIDs.timeBased());
EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE);
saveRelation(relationA);
saveRelation(relationB);
Assert.assertTrue(relationService.deleteRelation(relationA).get());
Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get());
Assert.assertTrue(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get());
Assert.assertTrue(relationService.deleteRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get());
}
@Test
public void testDeleteEntityRelations() throws ExecutionException, InterruptedException {
AssetId parentId = new AssetId(UUIDs.timeBased());
AssetId childId = new AssetId(UUIDs.timeBased());
AssetId subChildId = new AssetId(UUIDs.timeBased());
EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE);
saveRelation(relationA);
saveRelation(relationB);
Assert.assertTrue(relationService.deleteEntityRelations(childId).get());
Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get());
Assert.assertFalse(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get());
}
@Test
public void testFindFrom() throws ExecutionException, InterruptedException {
AssetId parentA = new AssetId(UUIDs.timeBased());
AssetId parentB = new AssetId(UUIDs.timeBased());
AssetId childA = new AssetId(UUIDs.timeBased());
AssetId childB = new AssetId(UUIDs.timeBased());
EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE);
EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE);
EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE);
EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE);
saveRelation(relationA1);
saveRelation(relationA2);
saveRelation(relationB1);
saveRelation(relationB2);
List<EntityRelation> relations = relationService.findByFrom(parentA).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType());
Assert.assertEquals(parentA, relation.getFrom());
Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
}
relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE).get();
Assert.assertEquals(2, relations.size());
relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE).get();
Assert.assertEquals(0, relations.size());
relations = relationService.findByFrom(parentB).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType());
Assert.assertEquals(parentB, relation.getFrom());
Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
}
relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get();
Assert.assertEquals(0, relations.size());
relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get();
Assert.assertEquals(0, relations.size());
}
private Boolean saveRelation(EntityRelation relationA1) throws ExecutionException, InterruptedException {
return relationService.saveRelation(relationA1).get();
}
@Test
public void testFindTo() throws ExecutionException, InterruptedException {
AssetId parentA = new AssetId(UUIDs.timeBased());
AssetId parentB = new AssetId(UUIDs.timeBased());
AssetId childA = new AssetId(UUIDs.timeBased());
AssetId childB = new AssetId(UUIDs.timeBased());
EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE);
EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE);
EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE);
EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE);
saveRelation(relationA1);
saveRelation(relationA2);
saveRelation(relationB1);
saveRelation(relationB2);
// Data propagation to views is async
Thread.sleep(3000);
List<EntityRelation> relations = relationService.findByTo(childA).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(childA, relation.getTo());
Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom()));
}
relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE).get();
Assert.assertEquals(1, relations.size());
relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE).get();
Assert.assertEquals(1, relations.size());
relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE).get();
Assert.assertEquals(0, relations.size());
relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE).get();
Assert.assertEquals(0, relations.size());
relations = relationService.findByTo(childB).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(childB, relation.getTo());
Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom()));
}
}
@Test
public void testCyclicRecursiveRelation() throws ExecutionException, InterruptedException {
// A -> B -> C -> A
AssetId assetA = new AssetId(UUIDs.timeBased());
AssetId assetB = new AssetId(UUIDs.timeBased());
AssetId assetC = new AssetId(UUIDs.timeBased());
EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE);
EntityRelation relationB = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE);
EntityRelation relationC = new EntityRelation(assetC, assetA, EntityRelation.CONTAINS_TYPE);
saveRelation(relationA);
saveRelation(relationB);
saveRelation(relationC);
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1));
query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
List<EntityRelation> relations = relationService.findByQuery(query).get();
Assert.assertEquals(3, relations.size());
Assert.assertTrue(relations.contains(relationA));
Assert.assertTrue(relations.contains(relationB));
Assert.assertTrue(relations.contains(relationC));
}
@Test
public void testRecursiveRelation() throws ExecutionException, InterruptedException {
// A -> B -> [C,D]
AssetId assetA = new AssetId(UUIDs.timeBased());
AssetId assetB = new AssetId(UUIDs.timeBased());
AssetId assetC = new AssetId(UUIDs.timeBased());
DeviceId deviceD = new DeviceId(UUIDs.timeBased());
EntityRelation relationAB = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE);
EntityRelation relationBC = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE);
EntityRelation relationBD = new EntityRelation(assetB, deviceD, EntityRelation.CONTAINS_TYPE);
saveRelation(relationAB);
saveRelation(relationBC);
saveRelation(relationBD);
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1));
query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
List<EntityRelation> relations = relationService.findByQuery(query).get();
Assert.assertEquals(2, relations.size());
Assert.assertTrue(relations.contains(relationAB));
Assert.assertTrue(relations.contains(relationBC));
}
@Test(expected = DataValidationException.class)
public void testSaveRelationWithEmptyFrom() throws ExecutionException, InterruptedException {
EntityRelation relation = new EntityRelation();
relation.setTo(new AssetId(UUIDs.timeBased()));
relation.setType(EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(saveRelation(relation));
}
@Test(expected = DataValidationException.class)
public void testSaveRelationWithEmptyTo() throws ExecutionException, InterruptedException {
EntityRelation relation = new EntityRelation();
relation.setFrom(new AssetId(UUIDs.timeBased()));
relation.setType(EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(saveRelation(relation));
}
@Test(expected = DataValidationException.class)
public void testSaveRelationWithEmptyType() throws ExecutionException, InterruptedException {
EntityRelation relation = new EntityRelation();
relation.setFrom(new AssetId(UUIDs.timeBased()));
relation.setTo(new AssetId(UUIDs.timeBased()));
Assert.assertTrue(saveRelation(relation));
}
}

26
dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java

@ -66,7 +66,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
saveEntries(deviceId, TS - 1); saveEntries(deviceId, TS - 1);
saveEntries(deviceId, TS); saveEntries(deviceId, TS);
ResultSetFuture rsFuture = tsService.findAllLatest(DataConstants.DEVICE, deviceId); ResultSetFuture rsFuture = tsService.findAllLatest(deviceId);
List<TsKvEntry> tsList = tsService.convertResultSetToTsKvEntryList(rsFuture.get()); List<TsKvEntry> tsList = tsService.convertResultSetToTsKvEntryList(rsFuture.get());
assertNotNull(tsList); assertNotNull(tsList);
@ -95,7 +95,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
saveEntries(deviceId, TS - 1); saveEntries(deviceId, TS - 1);
saveEntries(deviceId, TS); saveEntries(deviceId, TS);
List<ResultSet> rs = tsService.findLatest(DataConstants.DEVICE, deviceId, Collections.singleton(STRING_KEY)).get(); List<ResultSet> rs = tsService.findLatest(deviceId, Collections.singleton(STRING_KEY)).get();
Assert.assertEquals(1, rs.size()); Assert.assertEquals(1, rs.size());
Assert.assertEquals(toTsEntry(TS, stringKvEntry), tsService.convertResultToTsKvEntry(rs.get(0).one())); Assert.assertEquals(toTsEntry(TS, stringKvEntry), tsService.convertResultToTsKvEntry(rs.get(0).one()));
} }
@ -114,7 +114,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
entries.add(save(deviceId, 45000, 500)); entries.add(save(deviceId, 45000, 500));
entries.add(save(deviceId, 55000, 600)); entries.add(save(deviceId, 55000, 600));
List<TsKvEntry> list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.NONE))).get(); 60000, 20000, 3, Aggregation.NONE))).get();
assertEquals(3, list.size()); assertEquals(3, list.size());
assertEquals(55000, list.get(0).getTs()); assertEquals(55000, list.get(0).getTs());
@ -126,7 +126,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(35000, list.get(2).getTs()); assertEquals(35000, list.get(2).getTs());
assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue()); assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.AVG))).get(); 60000, 20000, 3, Aggregation.AVG))).get();
assertEquals(3, list.size()); assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs()); assertEquals(10000, list.get(0).getTs());
@ -138,7 +138,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs()); assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue()); assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.SUM))).get(); 60000, 20000, 3, Aggregation.SUM))).get();
assertEquals(3, list.size()); assertEquals(3, list.size());
@ -151,7 +151,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs()); assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue()); assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MIN))).get(); 60000, 20000, 3, Aggregation.MIN))).get();
assertEquals(3, list.size()); assertEquals(3, list.size());
@ -164,7 +164,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs()); assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue()); assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MAX))).get(); 60000, 20000, 3, Aggregation.MAX))).get();
assertEquals(3, list.size()); assertEquals(3, list.size());
@ -177,7 +177,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs()); assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue()); assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.COUNT))).get(); 60000, 20000, 3, Aggregation.COUNT))).get();
assertEquals(3, list.size()); assertEquals(3, list.size());
@ -193,15 +193,15 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception { private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception {
TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value)); TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value));
tsService.save(DataConstants.DEVICE, deviceId, entry).get(); tsService.save(deviceId, entry).get();
return entry; return entry;
} }
private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException { private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException {
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, stringKvEntry)).get(); tsService.save(deviceId, toTsEntry(ts, stringKvEntry)).get();
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, longKvEntry)).get(); tsService.save(deviceId, toTsEntry(ts, longKvEntry)).get();
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, doubleKvEntry)).get(); tsService.save(deviceId, toTsEntry(ts, doubleKvEntry)).get();
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, booleanKvEntry)).get(); tsService.save(deviceId, toTsEntry(ts, booleanKvEntry)).get();
} }
private static TsKvEntry toTsEntry(long ts, KvEntry entry) { private static TsKvEntry toTsEntry(long ts, KvEntry entry) {

4
docker/docker-compose.yml

@ -18,7 +18,7 @@ version: '2'
services: services:
thingsboard: thingsboard:
image: "thingsboard/application:1.2.2" image: "thingsboard/application:1.2.3"
ports: ports:
- "8080:8080" - "8080:8080"
- "1883:1883" - "1883:1883"
@ -27,7 +27,7 @@ services:
- thingsboard.env - thingsboard.env
entrypoint: ./run_thingsboard.sh entrypoint: ./run_thingsboard.sh
thingsboard-db-schema: thingsboard-db-schema:
image: "thingsboard/thingsboard-db-schema:1.2.2" image: "thingsboard/thingsboard-db-schema:1.2.3"
environment: environment:
- SKIP_SCHEMA_CREATION=${SKIP_SCHEMA_CREATION} - SKIP_SCHEMA_CREATION=${SKIP_SCHEMA_CREATION}
- SKIP_SYSTEM_DATA=${SKIP_SYSTEM_DATA} - SKIP_SYSTEM_DATA=${SKIP_SYSTEM_DATA}

4
docker/thingsboard-db-schema/build_and_deploy.sh

@ -20,9 +20,9 @@ cp ../../dao/src/main/resources/schema.cql schema.cql
cp ../../dao/src/main/resources/demo-data.cql demo-data.cql cp ../../dao/src/main/resources/demo-data.cql demo-data.cql
cp ../../dao/src/main/resources/system-data.cql system-data.cql cp ../../dao/src/main/resources/system-data.cql system-data.cql
docker build -t thingsboard/thingsboard-db-schema:1.2.2 -t thingsboard/thingsboard-db-schema:latest . docker build -t thingsboard/thingsboard-db-schema:1.2.3 -t thingsboard/thingsboard-db-schema:latest .
docker login docker login
docker push thingsboard/thingsboard-db-schema:1.2.2 docker push thingsboard/thingsboard-db-schema:1.2.3
docker push thingsboard/thingsboard-db-schema:latest docker push thingsboard/thingsboard-db-schema:latest

4
docker/thingsboard/build_and_deploy.sh

@ -18,9 +18,9 @@
cp ../../application/target/thingsboard.deb thingsboard.deb cp ../../application/target/thingsboard.deb thingsboard.deb
docker build -t thingsboard/application:1.2.2 -t thingsboard/application:latest . docker build -t thingsboard/application:1.2.3 -t thingsboard/application:latest .
docker login docker login
docker push thingsboard/application:1.2.2 docker push thingsboard/application:1.2.3
docker push thingsboard/application:latest docker push thingsboard/application:latest

2
extensions-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

5
extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/UnauthorizedException.java

@ -19,4 +19,9 @@ package org.thingsboard.server.extensions.api.exception;
* Created by ashvayka on 21.02.17. * Created by ashvayka on 21.02.17.
*/ */
public class UnauthorizedException extends Exception { public class UnauthorizedException extends Exception {
public UnauthorizedException(String message) {
super(message);
}
} }

26
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java

@ -49,7 +49,7 @@ public interface PluginContext {
Device RPC API Device RPC API
*/ */
Optional<ServerAddress> resolve(DeviceId deviceId); Optional<ServerAddress> resolve(EntityId entityId);
void getDevice(DeviceId deviceId, PluginCallback<Device> pluginCallback); void getDevice(DeviceId deviceId, PluginCallback<Device> pluginCallback);
@ -77,33 +77,33 @@ public interface PluginContext {
*/ */
void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback<Void> callback); void saveTsData(EntityId entityId, TsKvEntry entry, PluginCallback<Void> callback);
void saveTsData(DeviceId deviceId, List<TsKvEntry> entry, PluginCallback<Void> callback); void saveTsData(EntityId entityId, List<TsKvEntry> entry, PluginCallback<Void> callback);
void loadTimeseries(DeviceId deviceId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback); void loadTimeseries(EntityId entityId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback);
void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback); void loadLatestTimeseries(EntityId entityId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback);
void loadLatestTimeseries(DeviceId deviceId, PluginCallback<List<TsKvEntry>> callback); void loadLatestTimeseries(EntityId entityId, PluginCallback<List<TsKvEntry>> callback);
/* /*
Attributes API Attributes API
*/ */
void saveAttributes(TenantId tenantId, DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback); void saveAttributes(TenantId tenantId, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
void removeAttributes(TenantId tenantId, DeviceId deviceId, String scope, List<String> attributeKeys, PluginCallback<Void> callback); void removeAttributes(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys, PluginCallback<Void> callback);
void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback); void loadAttribute(EntityId entityId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback);
void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback); void loadAttributes(EntityId entityId, String attributeType, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback);
void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback); void loadAttributes(EntityId entityId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback);
void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, PluginCallback<List<AttributeKvEntry>> callback); void loadAttributes(EntityId entityId, Collection<String> attributeTypes, PluginCallback<List<AttributeKvEntry>> callback);
void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback); void loadAttributes(EntityId entityId, Collection<String> attributeTypes, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback);
void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback); void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback);
} }

7
extensions-core/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>1.2.3-SNAPSHOT</version> <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
@ -41,6 +41,11 @@
<artifactId>extensions-api</artifactId> <artifactId>extensions-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>transport</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

78
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/SubscriptionManager.java

@ -19,6 +19,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
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.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@ -39,7 +40,7 @@ import java.util.function.Function;
@Slf4j @Slf4j
public class SubscriptionManager { public class SubscriptionManager {
private final Map<DeviceId, Set<Subscription>> subscriptionsByDeviceId = new HashMap<>(); private final Map<EntityId, Set<Subscription>> subscriptionsByEntityId = new HashMap<>();
private final Map<String, Map<Integer, Subscription>> subscriptionsByWsSessionId = new HashMap<>(); private final Map<String, Map<Integer, Subscription>> subscriptionsByWsSessionId = new HashMap<>();
@ -48,28 +49,28 @@ public class SubscriptionManager {
@Setter @Setter
private TelemetryRpcMsgHandler rpcHandler; private TelemetryRpcMsgHandler rpcHandler;
public void addLocalWsSubscription(PluginContext ctx, String sessionId, DeviceId deviceId, SubscriptionState sub) { public void addLocalWsSubscription(PluginContext ctx, String sessionId, EntityId entityId, SubscriptionState sub) {
Optional<ServerAddress> server = ctx.resolve(deviceId); Optional<ServerAddress> server = ctx.resolve(entityId);
Subscription subscription; Subscription subscription;
if (server.isPresent()) { if (server.isPresent()) {
ServerAddress address = server.get(); ServerAddress address = server.get();
log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), deviceId, address); log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address);
subscription = new Subscription(sub, true, address); subscription = new Subscription(sub, true, address);
rpcHandler.onNewSubscription(ctx, address, sessionId, subscription); rpcHandler.onNewSubscription(ctx, address, sessionId, subscription);
} else { } else {
log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), deviceId); log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId);
subscription = new Subscription(sub, true); subscription = new Subscription(sub, true);
} }
registerSubscription(sessionId, deviceId, subscription); registerSubscription(sessionId, entityId, subscription);
} }
public void addRemoteWsSubscription(PluginContext ctx, ServerAddress address, String sessionId, Subscription subscription) { public void addRemoteWsSubscription(PluginContext ctx, ServerAddress address, String sessionId, Subscription subscription) {
DeviceId deviceId = subscription.getDeviceId(); EntityId entityId = subscription.getEntityId();
log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), deviceId, address); log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address);
registerSubscription(sessionId, deviceId, subscription); registerSubscription(sessionId, entityId, subscription);
if (subscription.getType() == SubscriptionType.ATTRIBUTES) { if (subscription.getType() == SubscriptionType.ATTRIBUTES) {
final Map<String, Long> keyStates = subscription.getKeyStates(); final Map<String, Long> keyStates = subscription.getKeyStates();
ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE, keyStates.keySet(), new PluginCallback<List<AttributeKvEntry>>() { ctx.loadAttributes(entityId, DataConstants.CLIENT_SCOPE, keyStates.keySet(), new PluginCallback<List<AttributeKvEntry>>() {
@Override @Override
public void onSuccess(PluginContext ctx, List<AttributeKvEntry> values) { public void onSuccess(PluginContext ctx, List<AttributeKvEntry> values) {
List<TsKvEntry> missedUpdates = new ArrayList<>(); List<TsKvEntry> missedUpdates = new ArrayList<>();
@ -95,7 +96,7 @@ public class SubscriptionManager {
queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs)); queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
}); });
ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() { ctx.loadTimeseries(entityId, queries, new PluginCallback<List<TsKvEntry>>() {
@Override @Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> missedUpdates) { public void onSuccess(PluginContext ctx, List<TsKvEntry> missedUpdates) {
if (!missedUpdates.isEmpty()) { if (!missedUpdates.isEmpty()) {
@ -112,11 +113,11 @@ public class SubscriptionManager {
} }
private void registerSubscription(String sessionId, DeviceId deviceId, Subscription subscription) { private void registerSubscription(String sessionId, EntityId entityId, Subscription subscription) {
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(subscription.getDeviceId()); Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(subscription.getEntityId());
if (deviceSubscriptions == null) { if (deviceSubscriptions == null) {
deviceSubscriptions = new HashSet<>(); deviceSubscriptions = new HashSet<>();
subscriptionsByDeviceId.put(deviceId, deviceSubscriptions); subscriptionsByEntityId.put(entityId, deviceSubscriptions);
} }
deviceSubscriptions.add(subscription); deviceSubscriptions.add(subscription);
Map<Integer, Subscription> sessionSubscriptions = subscriptionsByWsSessionId.get(sessionId); Map<Integer, Subscription> sessionSubscriptions = subscriptionsByWsSessionId.get(sessionId);
@ -133,7 +134,7 @@ public class SubscriptionManager {
if (sessionSubscriptions != null) { if (sessionSubscriptions != null) {
Subscription subscription = sessionSubscriptions.remove(subscriptionId); Subscription subscription = sessionSubscriptions.remove(subscriptionId);
if (subscription != null) { if (subscription != null) {
DeviceId deviceId = subscription.getDeviceId(); EntityId entityId = subscription.getEntityId();
if (subscription.isLocal() && subscription.getServer() != null) { if (subscription.isLocal() && subscription.getServer() != null) {
rpcHandler.onSubscriptionClose(ctx, subscription.getServer(), sessionId, subscription.getSubscriptionId()); rpcHandler.onSubscriptionClose(ctx, subscription.getServer(), sessionId, subscription.getSubscriptionId());
} }
@ -143,13 +144,13 @@ public class SubscriptionManager {
} else { } else {
log.debug("[{}] Removed session subscription.", sessionId); log.debug("[{}] Removed session subscription.", sessionId);
} }
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(deviceId); Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) { if (deviceSubscriptions != null) {
boolean result = deviceSubscriptions.remove(subscription); boolean result = deviceSubscriptions.remove(subscription);
if (result) { if (result) {
if (deviceSubscriptions.size() == 0) { if (deviceSubscriptions.size() == 0) {
log.debug("[{}] Removed last subscription for particular device.", sessionId); log.debug("[{}] Removed last subscription for particular device.", sessionId);
subscriptionsByDeviceId.remove(deviceId); subscriptionsByEntityId.remove(entityId);
} else { } else {
log.debug("[{}] Removed device subscription.", sessionId); log.debug("[{}] Removed device subscription.", sessionId);
} }
@ -167,8 +168,8 @@ public class SubscriptionManager {
} }
} }
public void onLocalSubscriptionUpdate(PluginContext ctx, DeviceId deviceId, SubscriptionType type, Function<Subscription, List<TsKvEntry>> f) { public void onLocalSubscriptionUpdate(PluginContext ctx, EntityId entityId, SubscriptionType type, Function<Subscription, List<TsKvEntry>> f) {
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(deviceId); Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) { if (deviceSubscriptions != null) {
deviceSubscriptions.stream().filter(s -> type == s.getType()).forEach(s -> { deviceSubscriptions.stream().filter(s -> type == s.getType()).forEach(s -> {
String sessionId = s.getWsSessionId(); String sessionId = s.getWsSessionId();
@ -184,7 +185,7 @@ public class SubscriptionManager {
} }
}); });
} else { } else {
log.debug("[{}] No device subscriptions to process!", deviceId); log.debug("[{}] No device subscriptions to process!", entityId);
} }
} }
@ -197,10 +198,10 @@ public class SubscriptionManager {
} }
} }
public void onAttributesUpdateFromServer(PluginContext ctx, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) { public void onAttributesUpdateFromServer(PluginContext ctx, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
Optional<ServerAddress> serverAddress = ctx.resolve(deviceId); Optional<ServerAddress> serverAddress = ctx.resolve(entityId);
if (!serverAddress.isPresent()) { if (!serverAddress.isPresent()) {
onLocalSubscriptionUpdate(ctx, deviceId, SubscriptionType.ATTRIBUTES, s -> { onLocalSubscriptionUpdate(ctx, entityId, SubscriptionType.ATTRIBUTES, s -> {
List<TsKvEntry> subscriptionUpdate = new ArrayList<TsKvEntry>(); List<TsKvEntry> subscriptionUpdate = new ArrayList<TsKvEntry>();
for (AttributeKvEntry kv : attributes) { for (AttributeKvEntry kv : attributes) {
if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) { if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
@ -210,7 +211,24 @@ public class SubscriptionManager {
return subscriptionUpdate; return subscriptionUpdate;
}); });
} else { } else {
rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), deviceId, scope, attributes); rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, scope, attributes);
}
}
public void onTimeseriesUpdateFromServer(PluginContext ctx, EntityId entityId, List<TsKvEntry> entries) {
Optional<ServerAddress> serverAddress = ctx.resolve(entityId);
if (!serverAddress.isPresent()) {
onLocalSubscriptionUpdate(ctx, entityId, SubscriptionType.TIMESERIES, s -> {
List<TsKvEntry> subscriptionUpdate = new ArrayList<TsKvEntry>();
for (TsKvEntry kv : entries) {
if (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey()))) {
subscriptionUpdate.add(kv);
}
}
return subscriptionUpdate;
});
} else {
rpcHandler.onTimeseriesUpdate(ctx, serverAddress.get(), entityId, entries);
} }
} }
@ -243,11 +261,11 @@ public class SubscriptionManager {
int sessionSubscriptionSize = sessionSubscriptions.size(); int sessionSubscriptionSize = sessionSubscriptions.size();
for (Subscription subscription : sessionSubscriptions.values()) { for (Subscription subscription : sessionSubscriptions.values()) {
DeviceId deviceId = subscription.getDeviceId(); EntityId entityId = subscription.getEntityId();
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(deviceId); Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
deviceSubscriptions.remove(subscription); deviceSubscriptions.remove(subscription);
if (deviceSubscriptions.isEmpty()) { if (deviceSubscriptions.isEmpty()) {
subscriptionsByDeviceId.remove(deviceId); subscriptionsByEntityId.remove(entityId);
} }
} }
subscriptionsByWsSessionId.remove(sessionId); subscriptionsByWsSessionId.remove(sessionId);
@ -272,9 +290,9 @@ public class SubscriptionManager {
public void onClusterUpdate(PluginContext ctx) { public void onClusterUpdate(PluginContext ctx) {
log.trace("Processing cluster onUpdate msg!"); log.trace("Processing cluster onUpdate msg!");
Iterator<Map.Entry<DeviceId, Set<Subscription>>> deviceIterator = subscriptionsByDeviceId.entrySet().iterator(); Iterator<Map.Entry<EntityId, Set<Subscription>>> deviceIterator = subscriptionsByEntityId.entrySet().iterator();
while (deviceIterator.hasNext()) { while (deviceIterator.hasNext()) {
Map.Entry<DeviceId, Set<Subscription>> e = deviceIterator.next(); Map.Entry<EntityId, Set<Subscription>> e = deviceIterator.next();
Set<Subscription> subscriptions = e.getValue(); Set<Subscription> subscriptions = e.getValue();
Optional<ServerAddress> newAddressOptional = ctx.resolve(e.getKey()); Optional<ServerAddress> newAddressOptional = ctx.resolve(e.getKey());
if (newAddressOptional.isPresent()) { if (newAddressOptional.isPresent()) {
@ -317,6 +335,6 @@ public class SubscriptionManager {
public void clear() { public void clear() {
subscriptionsByWsSessionId.clear(); subscriptionsByWsSessionId.clear();
subscriptionsByDeviceId.clear(); subscriptionsByEntityId.clear();
} }
} }

3
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java

@ -28,7 +28,8 @@ import lombok.NoArgsConstructor;
public class GetHistoryCmd implements TelemetryPluginCmd { public class GetHistoryCmd implements TelemetryPluginCmd {
private int cmdId; private int cmdId;
private String deviceId; private String entityType;
private String entityId;
private String keys; private String keys;
private long startTs; private long startTs;
private long endTs; private long endTs;

5
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/SubscriptionCmd.java

@ -26,7 +26,8 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT
public abstract class SubscriptionCmd implements TelemetryPluginCmd { public abstract class SubscriptionCmd implements TelemetryPluginCmd {
private int cmdId; private int cmdId;
private String deviceId; private String entityType;
private String entityId;
private String keys; private String keys;
private String scope; private String scope;
private boolean unsubscribe; private boolean unsubscribe;
@ -35,7 +36,7 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd {
@Override @Override
public String toString() { public String toString() {
return "SubscriptionCmd [deviceId=" + deviceId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]"; return "SubscriptionCmd [entityType=" + entityType + ", entityId=" + entityId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]";
} }
} }

29
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryFeature.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2017 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.extensions.core.plugin.telemetry.handlers;
/**
* Created by ashvayka on 08.05.17.
*/
public enum TelemetryFeature {
ATTRIBUTES, TIMESERIES;
public static TelemetryFeature forName(String name) {
return TelemetryFeature.valueOf(name.toUpperCase());
}
}

287
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java

@ -16,13 +16,19 @@
package org.thingsboard.server.extensions.core.plugin.telemetry.handlers; package org.thingsboard.server.extensions.core.plugin.telemetry.handlers;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
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.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.EntityIdFactory;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHandler; import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHandler;
@ -50,121 +56,116 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
public void handleHttpGetRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { public void handleHttpGetRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException {
RestRequest request = msg.getRequest(); RestRequest request = msg.getRequest();
String[] pathParams = request.getPathParams(); String[] pathParams = request.getPathParams();
if (pathParams.length >= 3) { if (pathParams.length < 4) {
String deviceIdStr = pathParams[0]; msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
String method = pathParams[1]; return;
String entity = pathParams[2]; }
String scope = pathParams.length >= 4 ? pathParams[3] : null;
if (StringUtils.isEmpty(method) || StringUtils.isEmpty(entity) || StringUtils.isEmpty(deviceIdStr)) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return;
}
DeviceId deviceId = DeviceId.fromString(deviceIdStr); String entityType = pathParams[0];
String entityIdStr = pathParams[1];
String method = pathParams[2];
TelemetryFeature feature = TelemetryFeature.forName(pathParams[3]);
String scope = pathParams.length >= 5 ? pathParams[4] : null;
if (StringUtils.isEmpty(entityType) || EntityType.valueOf(entityType) == null || StringUtils.isEmpty(entityIdStr) || StringUtils.isEmpty(method) || StringUtils.isEmpty(feature)) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return;
}
if (method.equals("keys")) { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
if (entity.equals("timeseries")) {
ctx.loadLatestTimeseries(deviceId, new PluginCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> value) {
List<String> keys = value.stream().map(tsKv -> tsKv.getKey()).collect(Collectors.toList());
msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK));
}
@Override if (method.equals("keys")) {
public void onFailure(PluginContext ctx, Exception e) { if (feature == TelemetryFeature.TIMESERIES) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); ctx.loadLatestTimeseries(entityId, new PluginCallback<List<TsKvEntry>>() {
} @Override
}); public void onSuccess(PluginContext ctx, List<TsKvEntry> value) {
} else if (entity.equals("attributes")) { List<String> keys = value.stream().map(tsKv -> tsKv.getKey()).collect(Collectors.toList());
PluginCallback<List<AttributeKvEntry>> callback = getAttributeKeysPluginCallback(msg); msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK));
if (!StringUtils.isEmpty(scope)) { }
ctx.loadAttributes(deviceId, scope, callback);
} else { @Override
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback); public void onFailure(PluginContext ctx, Exception e) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
} }
});
} else if (feature == TelemetryFeature.ATTRIBUTES) {
PluginCallback<List<AttributeKvEntry>> callback = getAttributeKeysPluginCallback(msg);
if (!StringUtils.isEmpty(scope)) {
ctx.loadAttributes(entityId, scope, callback);
} else {
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
} }
} else if (method.equals("values")) { }
if ("timeseries".equals(entity)) { } else if (method.equals("values")) {
String keysStr = request.getParameter("keys"); if (feature == TelemetryFeature.TIMESERIES) {
List<String> keys = Arrays.asList(keysStr.split(",")); String keysStr = request.getParameter("keys");
List<String> keys = Arrays.asList(keysStr.split(","));
Optional<Long> startTs = request.getLongParamValue("startTs"); Optional<Long> startTs = request.getLongParamValue("startTs");
Optional<Long> endTs = request.getLongParamValue("endTs"); Optional<Long> endTs = request.getLongParamValue("endTs");
Optional<Long> interval = request.getLongParamValue("interval"); Optional<Long> interval = request.getLongParamValue("interval");
Optional<Integer> limit = request.getIntParamValue("limit"); Optional<Integer> limit = request.getIntParamValue("limit");
if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) { if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) {
if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent()) { if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent()) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return; return;
} }
Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name())); Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name()));
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg))
.collect(Collectors.toList());
ctx.loadTimeseries(entityId, queries, getTsKvListCallback(msg));
} else {
ctx.loadLatestTimeseries(entityId, keys, getTsKvListCallback(msg));
}
} else if (feature == TelemetryFeature.ATTRIBUTES) {
String keys = request.getParameter("keys", "");
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)) PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg);
.collect(Collectors.toList()); if (!StringUtils.isEmpty(scope)) {
ctx.loadTimeseries(deviceId, queries, getTsKvListCallback(msg)); if (!StringUtils.isEmpty(keys)) {
List<String> keyList = Arrays.asList(keys.split(","));
ctx.loadAttributes(entityId, scope, keyList, callback);
} else { } else {
ctx.loadLatestTimeseries(deviceId, keys, getTsKvListCallback(msg)); ctx.loadAttributes(entityId, scope, callback);
} }
} else if ("attributes".equals(entity)) { } else {
String keys = request.getParameter("keys", ""); if (!StringUtils.isEmpty(keys)) {
List<String> keyList = Arrays.asList(keys.split(","));
PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg); ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), keyList, callback);
if (!StringUtils.isEmpty(scope)) {
if (!StringUtils.isEmpty(keys)) {
List<String> keyList = Arrays.asList(keys.split(","));
ctx.loadAttributes(deviceId, scope, keyList, callback);
} else {
ctx.loadAttributes(deviceId, scope, callback);
}
} else { } else {
if (!StringUtils.isEmpty(keys)) { ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
List<String> keyList = Arrays.asList(keys.split(","));
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), keyList, callback);
} else {
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
}
} }
} }
} }
} else {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
} }
private PluginCallback<List<TsKvEntry>> getTsKvListCallback(final PluginRestMsg msg) {
return new PluginCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
Map<String, List<TsData>> result = new LinkedHashMap<>();
for (TsKvEntry entry : data) {
List<TsData> vList = result.get(entry.getKey());
if (vList == null) {
vList = new ArrayList<>();
result.put(entry.getKey(), vList);
}
vList.add(new TsData(entry.getTs(), entry.getValueAsString()));
}
msg.getResponseHolder().setResult(new ResponseEntity<>(result, HttpStatus.OK));
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
log.error("Failed to fetch historical data", e);
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
};
}
@Override @Override
public void handleHttpPostRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { public void handleHttpPostRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException {
RestRequest request = msg.getRequest(); RestRequest request = msg.getRequest();
try { try {
String[] pathParams = request.getPathParams(); String[] pathParams = request.getPathParams();
EntityId entityId;
String scope;
TelemetryFeature feature;
if (pathParams.length == 2) { if (pathParams.length == 2) {
DeviceId deviceId = DeviceId.fromString(pathParams[0]); entityId = DeviceId.fromString(pathParams[0]);
String scope = pathParams[1]; scope = pathParams[1];
feature = TelemetryFeature.ATTRIBUTES;
} else if (pathParams.length == 3) {
entityId = EntityIdFactory.getByTypeAndId(pathParams[0], pathParams[1]);
scope = pathParams[2];
feature = TelemetryFeature.ATTRIBUTES;
} else if (pathParams.length == 4) {
entityId = EntityIdFactory.getByTypeAndId(pathParams[0], pathParams[1]);
feature = TelemetryFeature.forName(pathParams[2].toUpperCase());
scope = pathParams[3];
} else {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return;
}
if (feature == TelemetryFeature.ATTRIBUTES) {
if (DataConstants.SERVER_SCOPE.equals(scope) || if (DataConstants.SERVER_SCOPE.equals(scope) ||
DataConstants.SHARED_SCOPE.equals(scope)) { DataConstants.SHARED_SCOPE.equals(scope)) {
JsonNode jsonNode = jsonMapper.readTree(request.getRequestBody()); JsonNode jsonNode = jsonMapper.readTree(request.getRequestBody());
@ -185,11 +186,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
} }
}); });
if (attributes.size() > 0) { if (attributes.size() > 0) {
ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(() -> new IllegalArgumentException()).getTenantId(), deviceId, scope, attributes, new PluginCallback<Void>() { ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(() -> new IllegalArgumentException()).getTenantId(), entityId, scope, attributes, new PluginCallback<Void>() {
@Override @Override
public void onSuccess(PluginContext ctx, Void value) { public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
subscriptionManager.onAttributesUpdateFromServer(ctx, deviceId, scope, attributes); subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes);
} }
@Override @Override
@ -202,6 +203,28 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
} }
} }
} }
} else if (feature == TelemetryFeature.TIMESERIES) {
TelemetryUploadRequest telemetryRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(request.getRequestBody()));
List<TsKvEntry> entries = new ArrayList<>();
for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.getData().entrySet()) {
for (KvEntry kv : entry.getValue()) {
entries.add(new BasicTsKvEntry(entry.getKey(), kv));
}
}
ctx.saveTsData(entityId, entries, new PluginCallback<Void>() {
@Override
public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
subscriptionManager.onTimeseriesUpdateFromServer(ctx, entityId, entries);
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
log.error("Failed to save attributes", e);
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
});
return;
} }
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
log.debug("Failed to process POST request due to exception", e); log.debug("Failed to process POST request due to exception", e);
@ -214,29 +237,38 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
RestRequest request = msg.getRequest(); RestRequest request = msg.getRequest();
try { try {
String[] pathParams = request.getPathParams(); String[] pathParams = request.getPathParams();
EntityId entityId;
String scope;
if (pathParams.length == 2) { if (pathParams.length == 2) {
DeviceId deviceId = DeviceId.fromString(pathParams[0]); entityId = DeviceId.fromString(pathParams[0]);
String scope = pathParams[1]; scope = pathParams[1];
if (DataConstants.SERVER_SCOPE.equals(scope) || } else if (pathParams.length == 3) {
DataConstants.SHARED_SCOPE.equals(scope) || entityId = EntityIdFactory.getByTypeAndId(pathParams[0], pathParams[1]);
DataConstants.CLIENT_SCOPE.equals(scope)) { scope = pathParams[2];
String keysParam = request.getParameter("keys"); } else {
if (!StringUtils.isEmpty(keysParam)) { msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
String[] keys = keysParam.split(","); return;
ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(() -> new IllegalArgumentException()).getTenantId(), deviceId, scope, Arrays.asList(keys), new PluginCallback<Void>() { }
@Override
public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
}
@Override if (DataConstants.SERVER_SCOPE.equals(scope) ||
public void onFailure(PluginContext ctx, Exception e) { DataConstants.SHARED_SCOPE.equals(scope) ||
log.error("Failed to remove attributes", e); DataConstants.CLIENT_SCOPE.equals(scope)) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); String keysParam = request.getParameter("keys");
} if (!StringUtils.isEmpty(keysParam)) {
}); String[] keys = keysParam.split(",");
return; ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(() -> new IllegalArgumentException()).getTenantId(), entityId, scope, Arrays.asList(keys), new PluginCallback<Void>() {
} @Override
public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
log.error("Failed to remove attributes", e);
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
});
return;
} }
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -278,4 +310,29 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
} }
}; };
} }
private PluginCallback<List<TsKvEntry>> getTsKvListCallback(final PluginRestMsg msg) {
return new PluginCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
Map<String, List<TsData>> result = new LinkedHashMap<>();
for (TsKvEntry entry : data) {
List<TsData> vList = result.get(entry.getKey());
if (vList == null) {
vList = new ArrayList<>();
result.put(entry.getKey(), vList);
}
vList.add(new TsData(entry.getTs(), entry.getValueAsString()));
}
msg.getResponseHolder().setResult(new ResponseEntity<>(result, HttpStatus.OK));
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
log.error("Failed to fetch historical data", e);
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
};
}
} }

110
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRpcMsgHandler.java

@ -18,7 +18,8 @@ package org.thingsboard.server.extensions.core.plugin.telemetry.handlers;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.PluginContext;
@ -44,9 +45,10 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
private static final int SUBSCRIPTION_CLAZZ = 1; private static final int SUBSCRIPTION_CLAZZ = 1;
private static final int ATTRIBUTES_UPDATE_CLAZZ = 2; private static final int ATTRIBUTES_UPDATE_CLAZZ = 2;
private static final int SUBSCRIPTION_UPDATE_CLAZZ = 3; private static final int TIMESERIES_UPDATE_CLAZZ = 3;
private static final int SESSION_CLOSE_CLAZZ = 4; private static final int SUBSCRIPTION_UPDATE_CLAZZ = 4;
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 5; private static final int SESSION_CLOSE_CLAZZ = 5;
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 6;
@Override @Override
public void process(PluginContext ctx, RpcMsg msg) { public void process(PluginContext ctx, RpcMsg msg) {
@ -60,6 +62,9 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
case ATTRIBUTES_UPDATE_CLAZZ: case ATTRIBUTES_UPDATE_CLAZZ:
processAttributeUpdate(ctx, msg); processAttributeUpdate(ctx, msg);
break; break;
case TIMESERIES_UPDATE_CLAZZ:
processTimeseriesUpdate(ctx, msg);
break;
case SESSION_CLOSE_CLAZZ: case SESSION_CLOSE_CLAZZ:
processSessionClose(ctx, msg); processSessionClose(ctx, msg);
break; break;
@ -88,10 +93,21 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
subscriptionManager.onAttributesUpdateFromServer(ctx, DeviceId.fromString(proto.getDeviceId()), proto.getScope(), subscriptionManager.onAttributesUpdateFromServer(ctx, EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()), proto.getScope(),
proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList())); proto.getDataList().stream().map(this::toAttribute).collect(Collectors.toList()));
} }
private void processTimeseriesUpdate(PluginContext ctx, RpcMsg msg) {
TimeseriesUpdateProto proto;
try {
proto = TimeseriesUpdateProto.parseFrom(msg.getMsgData());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
subscriptionManager.onTimeseriesUpdateFromServer(ctx, EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()),
proto.getDataList().stream().map(this::toTimeseries).collect(Collectors.toList()));
}
private void processSubscriptionCmd(PluginContext ctx, RpcMsg msg) { private void processSubscriptionCmd(PluginContext ctx, RpcMsg msg) {
SubscriptionProto proto; SubscriptionProto proto;
try { try {
@ -101,7 +117,7 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
} }
Map<String, Long> statesMap = proto.getKeyStatesList().stream().collect(Collectors.toMap(SubscriptionKetStateProto::getKey, SubscriptionKetStateProto::getTs)); Map<String, Long> statesMap = proto.getKeyStatesList().stream().collect(Collectors.toMap(SubscriptionKetStateProto::getKey, SubscriptionKetStateProto::getTs));
Subscription subscription = new Subscription( Subscription subscription = new Subscription(
new SubscriptionState(proto.getSessionId(), proto.getSubscriptionId(), DeviceId.fromString(proto.getDeviceId()), SubscriptionType.valueOf(proto.getType()), proto.getAllKeys(), statesMap), new SubscriptionState(proto.getSessionId(), proto.getSubscriptionId(), EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()), SubscriptionType.valueOf(proto.getType()), proto.getAllKeys(), statesMap),
false, msg.getServerAddress()); false, msg.getServerAddress());
subscriptionManager.addRemoteWsSubscription(ctx, msg.getServerAddress(), proto.getSessionId(), subscription); subscriptionManager.addRemoteWsSubscription(ctx, msg.getServerAddress(), proto.getSessionId(), subscription);
} }
@ -110,7 +126,8 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
SubscriptionProto.Builder builder = SubscriptionProto.newBuilder(); SubscriptionProto.Builder builder = SubscriptionProto.newBuilder();
builder.setSessionId(sessionId); builder.setSessionId(sessionId);
builder.setSubscriptionId(cmd.getSubscriptionId()); builder.setSubscriptionId(cmd.getSubscriptionId());
builder.setDeviceId(cmd.getDeviceId().toString()); builder.setEntityType(cmd.getEntityId().getEntityType().name());
builder.setEntityId(cmd.getEntityId().getId().toString());
builder.setType(cmd.getType().name()); builder.setType(cmd.getType().name());
builder.setAllKeys(cmd.isAllKeys()); builder.setAllKeys(cmd.isAllKeys());
cmd.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build())); cmd.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build()));
@ -195,41 +212,62 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
} }
} }
public void onAttributesUpdate(PluginContext ctx, ServerAddress address, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) { public void onAttributesUpdate(PluginContext ctx, ServerAddress address, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
ctx.sendPluginRpcMsg(new RpcMsg(address, ATTRIBUTES_UPDATE_CLAZZ, getAttributesUpdateProto(deviceId, scope, attributes).toByteArray())); ctx.sendPluginRpcMsg(new RpcMsg(address, ATTRIBUTES_UPDATE_CLAZZ, getAttributesUpdateProto(entityId, scope, attributes).toByteArray()));
} }
private AttributeUpdateProto getAttributesUpdateProto(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes) { public void onTimeseriesUpdate(PluginContext ctx, ServerAddress address, EntityId entityId, List<TsKvEntry> tsKvEntries) {
ctx.sendPluginRpcMsg(new RpcMsg(address, TIMESERIES_UPDATE_CLAZZ, getTimeseriesUpdateProto(entityId, tsKvEntries).toByteArray()));
}
private TimeseriesUpdateProto getTimeseriesUpdateProto(EntityId entityId, List<TsKvEntry> tsKvEntries) {
TimeseriesUpdateProto.Builder builder = TimeseriesUpdateProto.newBuilder();
builder.setEntityId(entityId.getId().toString());
builder.setEntityType(entityId.getEntityType().name());
tsKvEntries.forEach(attr -> builder.addData(toKeyValueProto(attr.getTs(), attr).build()));
return builder.build();
}
private AttributeUpdateProto getAttributesUpdateProto(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
AttributeUpdateProto.Builder builder = AttributeUpdateProto.newBuilder(); AttributeUpdateProto.Builder builder = AttributeUpdateProto.newBuilder();
builder.setDeviceId(deviceId.toString()); builder.setEntityId(entityId.getId().toString());
builder.setEntityType(entityId.getEntityType().name());
builder.setScope(scope); builder.setScope(scope);
attributes.forEach( attributes.forEach(attr -> builder.addData(toKeyValueProto(attr.getLastUpdateTs(), attr).build()));
attr -> {
AttributeUpdateValueListProto.Builder dataBuilder = AttributeUpdateValueListProto.newBuilder();
dataBuilder.setKey(attr.getKey());
dataBuilder.setTs(attr.getLastUpdateTs());
dataBuilder.setValueType(attr.getDataType().ordinal());
switch (attr.getDataType()) {
case BOOLEAN:
dataBuilder.setBoolValue(attr.getBooleanValue().get());
break;
case LONG:
dataBuilder.setLongValue(attr.getLongValue().get());
break;
case DOUBLE:
dataBuilder.setDoubleValue(attr.getDoubleValue().get());
break;
case STRING:
dataBuilder.setStrValue(attr.getStrValue().get());
break;
}
builder.addData(dataBuilder.build());
}
);
return builder.build(); return builder.build();
} }
private AttributeKvEntry toAttribute(AttributeUpdateValueListProto proto) { private KeyValueProto.Builder toKeyValueProto(long ts, KvEntry attr) {
KeyValueProto.Builder dataBuilder = KeyValueProto.newBuilder();
dataBuilder.setKey(attr.getKey());
dataBuilder.setTs(ts);
dataBuilder.setValueType(attr.getDataType().ordinal());
switch (attr.getDataType()) {
case BOOLEAN:
dataBuilder.setBoolValue(attr.getBooleanValue().get());
break;
case LONG:
dataBuilder.setLongValue(attr.getLongValue().get());
break;
case DOUBLE:
dataBuilder.setDoubleValue(attr.getDoubleValue().get());
break;
case STRING:
dataBuilder.setStrValue(attr.getStrValue().get());
break;
}
return dataBuilder;
}
private AttributeKvEntry toAttribute(KeyValueProto proto) {
return new BaseAttributeKvEntry(getKvEntry(proto), proto.getTs());
}
private TsKvEntry toTimeseries(KeyValueProto proto) {
return new BasicTsKvEntry(proto.getTs(), getKvEntry(proto));
}
private KvEntry getKvEntry(KeyValueProto proto) {
KvEntry entry = null; KvEntry entry = null;
DataType type = DataType.values()[proto.getValueType()]; DataType type = DataType.values()[proto.getValueType()];
switch (type) { switch (type) {
@ -246,7 +284,7 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
entry = new StringDataEntry(proto.getKey(), proto.getStrValue()); entry = new StringDataEntry(proto.getKey(), proto.getStrValue());
break; break;
} }
return new BaseAttributeKvEntry(entry, proto.getTs()); return entry;
} }
} }

58
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java

@ -19,7 +19,8 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.extensions.api.exception.UnauthorizedException; import org.thingsboard.server.extensions.api.exception.UnauthorizedException;
import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@ -101,8 +102,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
if (cmd.isUnsubscribe()) { if (cmd.isUnsubscribe()) {
unsubscribe(ctx, cmd, sessionId); unsubscribe(ctx, cmd, sessionId);
} else if (validateSubscriptionCmd(ctx, sessionRef, cmd)) { } else if (validateSubscriptionCmd(ctx, sessionRef, cmd)) {
log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), cmd.getDeviceId()); EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId()); log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId);
Optional<Set<String>> keysOptional = getKeys(cmd); Optional<Set<String>> keysOptional = getKeys(cmd);
SubscriptionState sub; SubscriptionState sub;
if (keysOptional.isPresent()) { if (keysOptional.isPresent()) {
@ -118,8 +119,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
keys.forEach(key -> subState.put(key, 0L)); keys.forEach(key -> subState.put(key, 0L));
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState); SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, false, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
} }
@Override @Override
@ -138,9 +139,9 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
}; };
if (StringUtils.isEmpty(cmd.getScope())) { if (StringUtils.isEmpty(cmd.getScope())) {
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), keys, callback); ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), keys, callback);
} else { } else {
ctx.loadAttributes(deviceId, cmd.getScope(), keys, callback); ctx.loadAttributes(entityId, cmd.getScope(), keys, callback);
} }
} else { } else {
PluginCallback<List<AttributeKvEntry>> callback = new PluginCallback<List<AttributeKvEntry>>() { PluginCallback<List<AttributeKvEntry>> callback = new PluginCallback<List<AttributeKvEntry>>() {
@ -152,8 +153,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
Map<String, Long> subState = new HashMap<>(attributesData.size()); Map<String, Long> subState = new HashMap<>(attributesData.size());
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, true, subState); SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, true, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
} }
@Override @Override
@ -166,9 +167,9 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
}; };
if (StringUtils.isEmpty(cmd.getScope())) { if (StringUtils.isEmpty(cmd.getScope())) {
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback); ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
} else { } else {
ctx.loadAttributes(deviceId, cmd.getScope(), callback); ctx.loadAttributes(entityId, cmd.getScope(), callback);
} }
} }
} }
@ -183,33 +184,33 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
if (cmd.isUnsubscribe()) { if (cmd.isUnsubscribe()) {
unsubscribe(ctx, cmd, sessionId); unsubscribe(ctx, cmd, sessionId);
} else if (validateSubscriptionCmd(ctx, sessionRef, cmd)) { } else if (validateSubscriptionCmd(ctx, sessionRef, cmd)) {
DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId()); EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
Optional<Set<String>> keysOptional = getKeys(cmd); Optional<Set<String>> keysOptional = getKeys(cmd);
if (keysOptional.isPresent()) { if (keysOptional.isPresent()) {
long startTs; long startTs;
if (cmd.getTimeWindow() > 0) { if (cmd.getTimeWindow() > 0) {
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), cmd.getDeviceId()); log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId);
startTs = cmd.getStartTs(); startTs = cmd.getStartTs();
long endTs = cmd.getStartTs() + cmd.getTimeWindow(); long endTs = cmd.getStartTs() + cmd.getTimeWindow();
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList()); List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys)); ctx.loadTimeseries(entityId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys));
} else { } else {
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
startTs = System.currentTimeMillis(); startTs = System.currentTimeMillis();
log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), cmd.getDeviceId()); log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), entityId);
ctx.loadLatestTimeseries(deviceId, keys, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys)); ctx.loadLatestTimeseries(entityId, keys, getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys));
} }
} else { } else {
ctx.loadLatestTimeseries(deviceId, new PluginCallback<List<TsKvEntry>>() { ctx.loadLatestTimeseries(entityId, new PluginCallback<List<TsKvEntry>>() {
@Override @Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
Map<String, Long> subState = new HashMap<>(data.size()); Map<String, Long> subState = new HashMap<>(data.size());
data.forEach(v -> subState.put(v.getKey(), v.getTs())); data.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, true, subState); SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.TIMESERIES, true, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
} }
@Override @Override
@ -230,7 +231,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
} }
} }
private PluginCallback<List<TsKvEntry>> getSubscriptionCallback(final PluginWebsocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final DeviceId deviceId, final long startTs, final List<String> keys) { private PluginCallback<List<TsKvEntry>> getSubscriptionCallback(final PluginWebsocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) {
return new PluginCallback<List<TsKvEntry>>() { return new PluginCallback<List<TsKvEntry>>() {
@Override @Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
@ -239,8 +240,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
Map<String, Long> subState = new HashMap<>(keys.size()); Map<String, Long> subState = new HashMap<>(keys.size());
keys.forEach(key -> subState.put(key, startTs)); keys.forEach(key -> subState.put(key, startTs));
data.forEach(v -> subState.put(v.getKey(), v.getTs())); data.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState); SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.TIMESERIES, false, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
} }
@Override @Override
@ -263,7 +264,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);
return; return;
} }
if (cmd.getDeviceId() == null || cmd.getDeviceId().isEmpty()) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Device id is empty!"); "Device id is empty!");
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);
@ -275,10 +276,11 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);
return; return;
} }
DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId()); EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList()); List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() { .collect(Collectors.toList());
ctx.loadTimeseries(entityId, queries, new PluginCallback<List<TsKvEntry>>() {
@Override @Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
@ -321,7 +323,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
} }
private void unsubscribe(PluginContext ctx, SubscriptionCmd cmd, String sessionId) { private void unsubscribe(PluginContext ctx, SubscriptionCmd cmd, String sessionId) {
if (cmd.getDeviceId() == null || cmd.getDeviceId().isEmpty()) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
cleanupWebSocketSession(ctx, sessionId); cleanupWebSocketSession(ctx, sessionId);
} else { } else {
subscriptionManager.removeSubscription(ctx, sessionId, cmd.getCmdId()); subscriptionManager.removeSubscription(ctx, sessionId, cmd.getCmdId());
@ -329,7 +331,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
} }
private boolean validateSubscriptionCmd(PluginContext ctx, PluginWebsocketSessionRef sessionRef, SubscriptionCmd cmd) { private boolean validateSubscriptionCmd(PluginContext ctx, PluginWebsocketSessionRef sessionRef, SubscriptionCmd cmd) {
if (cmd.getDeviceId() == null || cmd.getDeviceId().isEmpty()) { if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Device id is empty!"); "Device id is empty!");
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save