Browse Source

Initial implementation of Asset, Relation and Alarm services

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

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.transport.auth.DeviceAuthService;
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.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
@ -80,6 +81,9 @@ public class ActorSystemContext {
@Autowired
@Getter private DeviceService deviceService;
@Autowired
@Getter private AssetService assetService;
@Autowired
@Getter private TenantService tenantService;

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.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
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.ListenableFuture;
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.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.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
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.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
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 akka.actor.ActorRef;
import org.w3c.dom.Attr;
import javax.annotation.Nullable;
@ -91,103 +93,107 @@ public final class PluginProcessingContext implements PluginContext {
}
@Override
public void saveAttributes(final TenantId tenantId, final DeviceId deviceId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
public void saveAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(entityId, scope, attributes);
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;
}), executor);
}));
}
@Override
public void removeAttributes(final TenantId tenantId, final DeviceId deviceId, final String scope, final List<String> keys, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys);
public void removeAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<String> keys, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(entityId, scope, keys);
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
public void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
public void loadAttribute(EntityId entityId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKey);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys);
public void loadAttributes(EntityId entityId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKeys);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(deviceId, attributeType);
public void loadAttributes(EntityId entityId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(entityId, attributeType);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadAttributes(final DeviceId deviceId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
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);
}));
}
@Override
public void loadAttributes(final DeviceId deviceId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
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);
}));
}
@Override
public void saveTsData(final DeviceId deviceId, final TsKvEntry entry, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry);
public void saveTsData(final EntityId entityId, final TsKvEntry entry, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(entityId, entry);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}));
}
@Override
public void saveTsData(final DeviceId deviceId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries);
public void saveTsData(final EntityId entityId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(entityId, entries);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}));
}
@Override
public void loadTimeseries(final DeviceId deviceId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, queries);
public void loadTimeseries(final EntityId entityId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(entityId, queries);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadLatestTimeseries(final DeviceId deviceId, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId);
public void loadLatestTimeseries(final EntityId entityId, final PluginCallback<List<TsKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ResultSetFuture future = pluginCtx.tsService.findAllLatest(entityId);
Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor);
}));
}
@Override
public void loadLatestTimeseries(final DeviceId deviceId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys);
public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys);
Futures.addCallback(rsListFuture, getListCallback(callback, rsList ->
{
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)));
}
private void validate(DeviceId deviceId, ValidationCallback callback) {
private void validate(EntityId entityId, ValidationCallback callback) {
if (securityCtx.isPresent()) {
final PluginApiCallSecurityContext ctx = securityCtx.get();
if (ctx.isTenantAdmin() || ctx.isCustomerUser()) {
ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(deviceId);
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;
if (ctx.isTenantAdmin() || ctx.isCustomerUser() || ctx.isSystemAdmin()) {
switch (entityId.getEntityType()) {
case DEVICE:
if (ctx.isSystemAdmin()) {
callback.onSuccess(this, Boolean.FALSE);
} 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 {
callback.onSuccess(this, Boolean.FALSE);
}
@ -297,8 +380,8 @@ public final class PluginProcessingContext implements PluginContext {
}
@Override
public Optional<ServerAddress> resolve(DeviceId deviceId) {
return pluginCtx.routingService.resolve(deviceId);
public Optional<ServerAddress> resolve(EntityId entityId) {
return pluginCtx.routingService.resolveById(entityId);
}
@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.controller.plugin.PluginWebSocketMsgEndpoint;
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.customer.CustomerService;
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.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
@ -46,7 +51,12 @@ public final class SharedPluginProcessingContext {
final ActorRef currentActor;
final ActorSystemContext systemContext;
final PluginWebSocketMsgEndpoint msgEndpoint;
final AssetService assetService;
final DeviceService deviceService;
final RuleService ruleService;
final PluginService pluginService;
final CustomerService customerService;
final TenantService tenantService;
final TimeseriesService tsService;
final AttributesService attributesService;
final ClusterRpcService rpcService;
@ -65,9 +75,14 @@ public final class SharedPluginProcessingContext {
this.msgEndpoint = sysContext.getWsMsgEndpoint();
this.tsService = sysContext.getTsService();
this.attributesService = sysContext.getAttributesService();
this.assetService = sysContext.getAssetService();
this.deviceService = sysContext.getDeviceService();
this.rpcService = sysContext.getRpcService();
this.routingService = sysContext.getRoutingService();
this.ruleService = sysContext.getRuleService();
this.pluginService = sysContext.getPluginService();
this.customerService = sysContext.getCustomerService();
this.tenantService = sysContext.getTenantService();
}
public PluginId getPluginId() {
@ -89,7 +104,7 @@ public final class SharedPluginProcessingContext {
}
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()) {
log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, 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) {
action.accept(ctx);
} 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
public void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId) {
DeviceCredentialsUpdateNotificationMsg msg = new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceId);
Optional<ServerAddress> address = actorContext.getRoutingService().resolve(deviceId);
Optional<ServerAddress> address = actorContext.getRoutingService().resolveById(deviceId);
if (address.isPresent()) {
rpcService.tell(address.get(), msg);
} else {

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

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

83
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.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageLink;
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.widget.WidgetType;
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.dashboard.DashboardService;
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.model.ModelConstants;
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.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
@ -80,6 +80,9 @@ public abstract class BaseController {
@Autowired
protected DeviceService deviceService;
@Autowired
protected AssetService assetService;
@Autowired
protected DeviceCredentialsService deviceCredentialsService;
@ -104,6 +107,9 @@ public abstract class BaseController {
@Autowired
protected ActorService actorService;
@Autowired
protected RelationService relationService;
@ExceptionHandler(ThingsboardException.class)
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 {
try {
validateId(deviceId, "Incorrect deviceId " + deviceId);
@ -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 {
try {
validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
@ -387,6 +449,16 @@ public abstract class BaseController {
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 {
checkNotNull(rule);
SecurityUser authUser = getCurrentUser();
@ -412,7 +484,8 @@ public abstract class BaseController {
if (request.getHeader("x-forwarded-port") != null) {
try {
serverPort = request.getIntHeader("x-forwarded-port");
} catch (NumberFormatException e) {}
} catch (NumberFormatException e) {
}
}
String baseUrl = String.format("%s://%s:%d",

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.security.access.prepost.PreAuthorize;
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.id.*;
import org.thingsboard.server.common.data.page.TimePageData;
@ -59,7 +58,7 @@ public class EventController extends BaseController {
ThingsboardErrorCode.PERMISSION_DENIED);
}
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) {
throw handleException(e);
}
@ -88,29 +87,10 @@ public class EventController extends BaseController {
ThingsboardErrorCode.PERMISSION_DENIED);
}
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) {
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;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.Optional;
import java.util.UUID;
/**
* @author Andrew Shvayka
@ -28,6 +30,8 @@ public interface ClusterRoutingService {
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.stereotype.Service;
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.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
@ -31,6 +32,7 @@ import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
@ -77,13 +79,18 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService, D
}
@Override
public Optional<ServerAddress> resolve(UUIDBased entityId) {
Assert.notNull(entityId);
public Optional<ServerAddress> resolveById(EntityId entityId) {
return resolveByUuid(entityId.getId());
}
@Override
public Optional<ServerAddress> resolveByUuid(UUID uuid) {
Assert.notNull(uuid);
if (circle.isEmpty()) {
return Optional.empty();
}
Long hash = hashFunction.newHasher().putLong(entityId.getId().getMostSignificantBits())
.putLong(entityId.getId().getLeastSignificantBits()).hash().asLong();
Long hash = hashFunction.newHasher().putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits()).hash().asLong();
if (!circle.containsKey(hash)) {
ConcurrentNavigableMap<Long, ServerInstance> tailMap =
circle.tailMap(hash);

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

@ -60,8 +60,8 @@ plugins:
# JWT Token parameters
security.jwt:
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000000}" # Number of seconds (15 mins)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:36000000}" # Seconds (1 hour)
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
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);
when(routingService.resolve(any())).thenReturn(Optional.empty());
when(routingService.resolveById((EntityId) any())).thenReturn(Optional.empty());
when(discoveryService.getCurrentServer()).thenReturn(serverInstance);
@ -239,7 +239,7 @@ public class DefaultActorServiceTest {
List<TsKvEntry> expected = new ArrayList<>();
expected.add(new BasicTsKvEntry(ts, entry1));
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/src/main/java/org/thingsboard/server/common/data/EntityType.java

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

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

@ -0,0 +1,51 @@
/**
* 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.AssetId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
/**
* Created by ashvayka on 11.05.17.
*/
@Data
public class Alarm extends BaseData<AlarmId> {
private TenantId tenantId;
private String type;
private EntityId originator;
private AlarmSeverity severity;
private AlarmStatus status;
private long startTs;
private long endTs;
private long ackTs;
private long clearTs;
private JsonNode details;
private boolean propagate;
public Alarm() {
super();
}
public Alarm(AlarmId id) {
super(id);
}
}

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

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

@ -0,0 +1,34 @@
/**
* 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.id.TenantId;
import org.thingsboard.server.common.data.page.TimePageLink;
/**
* Created by ashvayka on 11.05.17.
*/
@Data
public class AlarmQuery {
private TenantId tenantId;
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;
}

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

@ -0,0 +1,33 @@
/**
* 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;
public boolean isAck() {
return this == ACTIVE_ACK || this == CLEARED_ACK;
}
public boolean isCleared() {
return this == CLEARED_ACK || this == CLEARED_UNACK;
}
}

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 com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
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
public DashboardId(@JsonProperty("id") UUID id){
public DashboardId(@JsonProperty("id") UUID 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;
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 java.util.UUID;
@ -23,6 +25,9 @@ import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@JsonDeserialize(using = EntityIdDeserializer.class)
@JsonSerialize(using = EntityIdSerializer.class)
public interface EntityId {
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 com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
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
public UserId(@JsonProperty("id") UUID id){
super(id);
}
public UserId(@JsonProperty("id") UUID 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);
}
}

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

@ -0,0 +1,34 @@
/**
* 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.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.model.AlarmEntity;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmDao extends Dao<AlarmEntity> {
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
AlarmEntity save(Alarm alarm);
}

53
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java

@ -0,0 +1,53 @@
/**
* 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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.AbstractModelDao;
import org.thingsboard.server.dao.model.AlarmEntity;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
@Component
@Slf4j
public class AlarmDaoImpl extends AbstractModelDao<AlarmEntity> implements AlarmDao {
@Override
protected Class<AlarmEntity> getColumnFamilyClass() {
return AlarmEntity.class;
}
@Override
protected String getColumnFamilyName() {
return ALARM_COLUMN_FAMILY_NAME;
}
@Override
public AlarmEntity save(Alarm alarm) {
log.debug("Save asset [{}] ", alarm);
return save(new AlarmEntity(alarm));
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return null;
}
}

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

@ -0,0 +1,42 @@
/**
* 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.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageData;
import java.util.Optional;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmService {
Alarm createOrUpdateAlarm(Alarm alarm);
ListenableFuture<Boolean> updateAlarm(Alarm alarm);
ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTs);
ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
}

266
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -0,0 +1,266 @@
/**
* 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.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.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
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.page.TimePageData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.AssetSearchQuery;
import org.thingsboard.server.dao.asset.AssetService;
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.AlarmEntity;
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.relation.RelationService;
import org.thingsboard.server.dao.relation.RelationsSearchParameters;
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.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
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 BaseAlarmService extends BaseEntityService implements AlarmService {
private static final String ALARM_RELATION_PREFIX = "ALARM_";
private static final String ALARM_RELATION = "ALARM_ANY";
@Autowired
private AlarmDao alarmDao;
@Autowired
private TenantDao tenantDao;
@Autowired
private RelationService relationService;
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
alarmDataValidator.validate(alarm);
try {
if (alarm.getStartTs() == 0L) {
alarm.setStartTs(System.currentTimeMillis());
}
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
if (existing == null || existing.getStatus().isCleared()) {
log.debug("New Alarm : {}", alarm);
Alarm saved = getData(alarmDao.save(new AlarmEntity(alarm)));
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
for (EntityId parentId : parentEntities) {
createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION));
createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name()));
}
return saved;
} else {
log.debug("Alarm before merge: {}", alarm);
alarm = merge(existing, alarm);
log.debug("Alarm after merge: {}", alarm);
return getData(alarmDao.save(new AlarmEntity(alarm)));
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public ListenableFuture<Boolean> updateAlarm(Alarm update) {
alarmDataValidator.validate(update);
return getAndUpdate(update.getId(), new Function<AlarmEntity, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable AlarmEntity entity) {
Alarm alarm = getData(entity);
if (alarm == null) {
return false;
} else {
AlarmStatus oldStatus = alarm.getStatus();
AlarmStatus newStatus = update.getStatus();
alarmDao.save(new AlarmEntity(merge(alarm, update)));
if (oldStatus != newStatus) {
updateRelations(alarm, oldStatus, newStatus);
}
return true;
}
}
});
}
@Override
public ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTime) {
return getAndUpdate(alarmId, new Function<AlarmEntity, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable AlarmEntity entity) {
Alarm alarm = getData(entity);
if (alarm == null || alarm.getStatus().isAck()) {
return false;
} else {
AlarmStatus oldStatus = alarm.getStatus();
AlarmStatus newStatus = oldStatus.isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK;
alarm.setStatus(newStatus);
alarm.setAckTs(ackTime);
alarmDao.save(new AlarmEntity(alarm));
updateRelations(alarm, oldStatus, newStatus);
return true;
}
}
});
}
@Override
public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTime) {
return getAndUpdate(alarmId, new Function<AlarmEntity, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable AlarmEntity entity) {
Alarm alarm = getData(entity);
if (alarm == null || alarm.getStatus().isCleared()) {
return false;
} else {
AlarmStatus oldStatus = alarm.getStatus();
AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK;
alarm.setStatus(newStatus);
alarm.setClearTs(clearTime);
alarmDao.save(new AlarmEntity(alarm));
updateRelations(alarm, oldStatus, newStatus);
return true;
}
}
});
}
@Override
public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) {
return null;
}
private void deleteRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException {
log.debug("Deleting Alarm relation: {}", alarmRelation);
relationService.deleteRelation(alarmRelation).get();
}
private void createRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException {
log.debug("Creating Alarm relation: {}", alarmRelation);
relationService.saveRelation(alarmRelation).get();
}
private Alarm merge(Alarm existing, Alarm alarm) {
if (alarm.getStartTs() > existing.getEndTs()) {
existing.setEndTs(alarm.getStartTs());
}
if (alarm.getEndTs() > existing.getEndTs()) {
existing.setEndTs(alarm.getEndTs());
}
if (alarm.getClearTs() > existing.getClearTs()) {
existing.setClearTs(alarm.getClearTs());
}
if (alarm.getAckTs() > existing.getAckTs()) {
existing.setAckTs(alarm.getAckTs());
}
existing.setStatus(alarm.getStatus());
existing.setSeverity(alarm.getSeverity());
existing.setDetails(alarm.getDetails());
return existing;
}
private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) {
try {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
for (EntityId parentId : parentEntities) {
deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name()));
createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name()));
}
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus);
throw new RuntimeException(e);
}
}
private ListenableFuture<Boolean> getAndUpdate(AlarmId alarmId, Function<AlarmEntity, Boolean> function) {
validateId(alarmId, "Alarm id should be specified!");
ListenableFuture<AlarmEntity> entity = alarmDao.findByIdAsync(alarmId.getId());
return Futures.transform(entity, function);
}
private DataValidator<Alarm> alarmDataValidator =
new DataValidator<Alarm>() {
@Override
protected void validateDataImpl(Alarm alarm) {
if (StringUtils.isEmpty(alarm.getType())) {
throw new DataValidationException("Alarm type should be specified!");
}
if (alarm.getOriginator() == null) {
throw new DataValidationException("Alarm originator should be specified!");
}
if (alarm.getSeverity() == null) {
throw new DataValidationException("Alarm severity should be specified!");
}
if (alarm.getStatus() == null) {
throw new DataValidationException("Alarm status should be specified!");
}
if (alarm.getTenantId() == null) {
throw new DataValidationException("Alarm should be assigned to tenant!");
} else {
TenantEntity tenant = tenantDao.findById(alarm.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
}
}
};
}

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;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -23,16 +24,18 @@ import org.thingsboard.server.common.data.page.TextPageLink;
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);
public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink);
Customer findOrCreatePublicCustomer(TenantId tenantId);
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.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
import java.io.IOException;
import java.util.List;
@ -24,31 +25,35 @@ import java.util.Optional;
import com.fasterxml.jackson.databind.JsonNode;
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 org.apache.commons.lang3.StringUtils;
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.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.dashboard.DashboardService;
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.IncorrectParameterException;
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.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
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.stereotype.Service;
import org.thingsboard.server.dao.service.Validator;
@Service
@Slf4j
public class CustomerServiceImpl implements CustomerService {
public class CustomerServiceImpl extends BaseEntityService implements CustomerService {
private static final String PUBLIC_CUSTOMER_TITLE = "Public";
@ -75,6 +80,14 @@ public class CustomerServiceImpl implements CustomerService {
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
public Customer saveCustomer(Customer customer) {
log.trace("Executing saveCustomer [{}]", customer);
@ -93,7 +106,8 @@ public class CustomerServiceImpl implements CustomerService {
}
dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
userService.deleteCustomerUsers(customer.getTenantId(), customerId);
userService.deleteCustomerUsers(customer.getTenantId(), customerId);
deleteEntityRelations(customerId);
customerDao.removeById(customerId.getId());
}

6
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.TextPageLink;
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.*;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
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.stereotype.Service;
import org.thingsboard.server.dao.service.Validator;
@Service
@Slf4j
public class DashboardServiceImpl implements DashboardService {
public class DashboardServiceImpl extends BaseEntityService implements DashboardService {
@Autowired
private DashboardDao dashboardDao;
@ -91,6 +90,7 @@ public class DashboardServiceImpl implements DashboardService {
public void deleteDashboard(DashboardId dashboardId) {
log.trace("Executing deleteDashboard [{}]", dashboardId);
Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
deleteEntityRelations(dashboardId);
dashboardDao.removeById(dashboardId.getId());
}

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

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
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.CustomerEntity;
import org.thingsboard.server.dao.model.DeviceEntity;
@ -53,7 +54,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@Slf4j
public class DeviceServiceImpl implements DeviceService {
public class DeviceServiceImpl extends BaseEntityService implements DeviceService {
@Autowired
private DeviceDao deviceDao;
@ -132,6 +133,7 @@ public class DeviceServiceImpl implements DeviceService {
if (deviceCredentials != null) {
deviceCredentialsService.deleteDeviceCredentials(deviceCredentials);
}
deleteEntityRelations(deviceId);
deviceDao.removeById(deviceId.getId());
}

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

148
dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java

@ -0,0 +1,148 @@
/**
* 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.*;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.type.AlarmSeverityCodec;
import org.thingsboard.server.dao.model.type.AlarmStatusCodec;
import org.thingsboard.server.dao.model.type.EntityTypeCodec;
import org.thingsboard.server.dao.model.type.JsonCodec;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@Table(name = ALARM_COLUMN_FAMILY_NAME)
public final class AlarmEntity implements BaseEntity<Alarm> {
@Transient
private static final long serialVersionUID = -1265181166886910152L;
@ClusteringColumn(value = 1)
@Column(name = ID_PROPERTY)
private UUID id;
@PartitionKey(value = 0)
@Column(name = ALARM_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 1)
@Column(name = ALARM_ORIGINATOR_ID_PROPERTY)
private UUID originatorId;
@PartitionKey(value = 2)
@Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY, codec = EntityTypeCodec.class)
private EntityType originatorType;
@ClusteringColumn(value = 0)
@Column(name = ALARM_TYPE_PROPERTY)
private String type;
@Column(name = ALARM_SEVERITY_PROPERTY, codec = AlarmSeverityCodec.class)
private AlarmSeverity severity;
@Column(name = ALARM_STATUS_PROPERTY, codec = AlarmStatusCodec.class)
private AlarmStatus status;
@Column(name = ALARM_START_TS_PROPERTY)
private Long startTs;
@Column(name = ALARM_END_TS_PROPERTY)
private Long endTs;
@Column(name = ALARM_ACK_TS_PROPERTY)
private Long ackTs;
@Column(name = ALARM_CLEAR_TS_PROPERTY)
private Long clearTs;
@Column(name = ALARM_DETAILS_PROPERTY, codec = JsonCodec.class)
private JsonNode details;
@Column(name = ALARM_PROPAGATE_PROPERTY)
private Boolean propagate;
public AlarmEntity() {
super();
}
public AlarmEntity(Alarm alarm) {
if (alarm.getId() != null) {
this.id = alarm.getId().getId();
}
if (alarm.getTenantId() != null) {
this.tenantId = alarm.getTenantId().getId();
}
this.type = alarm.getType();
this.originatorId = alarm.getOriginator().getId();
this.originatorType = alarm.getOriginator().getEntityType();
this.type = alarm.getType();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.propagate = alarm.isPropagate();
this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails();
}
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;
}
@Override
public Alarm toData() {
Alarm alarm = new Alarm(new AlarmId(id));
alarm.setCreatedTime(UUIDs.unixTimestamp(id));
if (tenantId != null) {
alarm.setTenantId(new TenantId(tenantId));
}
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
alarm.setType(type);
alarm.setSeverity(severity);
alarm.setStatus(status);
alarm.setPropagate(propagate);
alarm.setStartTs(startTs);
alarm.setEndTs(endTs);
alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs);
alarm.setDetails(details);
return alarm;
}
}

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

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
@NoArgsConstructor
@Table(name = DEVICE_COLUMN_FAMILY_NAME)
@Table(name = EVENT_COLUMN_FAMILY_NAME)
public class EventEntity implements BaseEntity<Event> {
@Transient
@ -98,23 +98,7 @@ public class EventEntity implements BaseEntity<Event> {
Event event = new Event(new EventId(id));
event.setCreatedTime(UUIDs.unixTimestamp(id));
event.setTenantId(new TenantId(tenantId));
switch (entityType) {
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.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
event.setBody(body);
event.setType(eventType);
event.setUid(eventUId);

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

@ -126,6 +126,49 @@ public class ModelConstants {
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";
/**
* 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 alarm constants.
*/
public static final String ALARM_COLUMN_FAMILY_NAME = "alarm";
public static final String ALARM_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
public static final String ALARM_TYPE_PROPERTY = "type";
public static final String ALARM_DETAILS_PROPERTY = "details";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String ALARM_SEVERITY_PROPERTY = "severity";
public static final String ALARM_STATUS_PROPERTY = "status";
public static final String ALARM_START_TS_PROPERTY = "start_ts";
public static final String ALARM_END_TS_PROPERTY = "end_ts";
public static final String ALARM_ACK_TS_PROPERTY = "ack_ts";
public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
/**
* 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.

29
dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmSeverityCodec.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.dao.model.type;
import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.dao.alarm.AlarmService;
public class AlarmSeverityCodec extends EnumNameCodec<AlarmSeverity> {
public AlarmSeverityCodec() {
super(AlarmSeverity.class);
}
}

27
dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmStatusCodec.java

@ -0,0 +1,27 @@
/**
* 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.type;
import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
public class AlarmStatusCodec extends EnumNameCodec<AlarmStatus> {
public AlarmStatusCodec() {
super(AlarmStatus.class);
}
}

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

@ -15,13 +15,14 @@
*/
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 org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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.page.TextPageData;
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.ComponentType;
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.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException;
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.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
public class BasePluginService implements PluginService {
public class BasePluginService extends BaseEntityService implements PluginService {
//TODO: move to a better place.
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));
}
@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
public PluginMetaData findPluginByApiToken(String apiToken) {
Validator.validateString(apiToken, "Incorrect plugin apiToken for search request.");
@ -205,6 +216,7 @@ public class BasePluginService implements PluginService {
@Override
public void deletePluginById(PluginId pluginId) {
Validator.validateId(pluginId, "Incorrect plugin id for delete request.");
deleteEntityRelations(pluginId);
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;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
@ -29,6 +30,8 @@ public interface PluginService {
PluginMetaData findPluginById(PluginId pluginId);
ListenableFuture<PluginMetaData> findPluginByIdAsync(PluginId pluginId);
PluginMetaData findPluginByApiToken(String apiToken);
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);
}
}

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

@ -0,0 +1,261 @@
/**
* 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.debug("Filters are not set [{}]", query);
}
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) {
if (filters == null || filters.isEmpty()) {
relations.add(relation);
} else {
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);
}

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

@ -0,0 +1,52 @@
/**
* 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);
// TODO: This method may be useful for some validations in the future
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
}

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.node.ArrayNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.TenantId;
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.rule.RuleMetaData;
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.DatabaseException;
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.plugin.PluginService;
import org.thingsboard.server.dao.service.DataValidator;
@ -53,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@Slf4j
public class BaseRuleService implements RuleService {
public class BaseRuleService extends BaseEntityService implements RuleService {
private final TenantId systemTenantId = new TenantId(NULL_UUID);
@ -166,6 +171,13 @@ public class BaseRuleService implements RuleService {
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
public List<RuleMetaData> findPluginRules(String pluginToken) {
List<RuleMetaDataEntity> ruleEntities = ruleDao.findRulesByPlugin(pluginToken);
@ -235,6 +247,7 @@ public class BaseRuleService implements RuleService {
@Override
public void deleteRuleById(RuleId ruleId) {
validateId(ruleId, "Incorrect rule id for delete rule request.");
deleteEntityRelations(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;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
@ -29,6 +30,8 @@ public interface RuleService {
RuleMetaData findRuleById(RuleId ruleId);
ListenableFuture<RuleMetaData> findRuleByIdAsync(RuleId ruleId);
List<RuleMetaData> findPluginRules(String pluginToken);
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;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -24,6 +25,19 @@ import java.util.UUID;
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
* <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;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
@ -22,16 +23,16 @@ import org.thingsboard.server.common.data.page.TextPageLink;
public interface TenantService {
public Tenant findTenantById(TenantId tenantId);
public Tenant saveTenant(Tenant tenant);
Tenant findTenantById(TenantId tenantId);
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.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
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 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.id.TenantId;
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.dashboard.DashboardService;
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.model.CustomerEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
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.stereotype.Service;
import org.thingsboard.server.dao.service.Validator;
@ -45,19 +50,19 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
@Service
@Slf4j
public class TenantServiceImpl implements TenantService {
public class TenantServiceImpl extends BaseEntityService implements TenantService {
private static final String DEFAULT_TENANT_REGION = "Global";
@Autowired
private TenantDao tenantDao;
@Autowired
private UserService userService;
@Autowired
private CustomerService customerService;
@Autowired
private DeviceService deviceService;
@ -72,7 +77,7 @@ public class TenantServiceImpl implements TenantService {
@Autowired
private PluginService pluginService;
@Override
public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId);
@ -81,6 +86,14 @@ public class TenantServiceImpl implements TenantService {
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
public Tenant saveTenant(Tenant tenant) {
log.trace("Executing saveTenant [{}]", tenant);
@ -102,6 +115,7 @@ public class TenantServiceImpl implements TenantService {
ruleService.deleteRulesByTenantId(tenantId);
pluginService.deletePluginsByTenantId(tenantId);
tenantDao.removeById(tenantId.getId());
deleteEntityRelations(tenantId);
}
@Override
@ -131,10 +145,10 @@ public class TenantServiceImpl implements TenantService {
}
}
};
private PaginatedRemover<String, TenantEntity> tenantsRemover =
new PaginatedRemover<String, TenantEntity>() {
@Override
protected List<TenantEntity> findEntities(String region, TextPageLink 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 org.springframework.beans.factory.annotation.Value;
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.DataType;
import org.thingsboard.server.dao.AbstractAsyncDao;
@ -94,8 +95,8 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
}
@Override
public ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityType, entityId, query)).collect(Collectors.toList());
public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
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>>() {
@Nullable
@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) {
return findAllAsyncWithLimit(entityType, entityId, query);
return findAllAsyncWithLimit(entityId, query);
} else {
long step = Math.max(query.getInterval(), minAggregationStepMs);
long stepTs = query.getStartTs();
@ -119,7 +120,7 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
long startTs = stepTs;
long endTs = stepTs + step;
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;
}
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 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 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>>() {
@Override
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);
}
@Override
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);
@ -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 String key = query.getKey();
final long startTs = query.getStartTs();
final long endTs = query.getEndTs();
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<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);
}
@ -209,21 +210,21 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
.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 -> {
try {
PreparedStatement proto = getFetchStmt(aggregation);
List<ResultSetFuture> futures = new ArrayList<>(partitions.size());
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();
stmt.setString(0, entityType);
stmt.setUUID(1, entityId);
stmt.setString(0, entityId.getEntityType().name());
stmt.setUUID(1, entityId.getId());
stmt.setString(2, key);
stmt.setLong(3, partition);
stmt.setLong(4, startTs);
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));
}
return Futures.allAsList(futures);
@ -235,30 +236,30 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
}
@Override
public ResultSetFuture findLatest(String entityType, UUID entityId, String key) {
public ResultSetFuture findLatest(EntityId entityId, String key) {
BoundStatement stmt = getFindLatestStmt().bind();
stmt.setString(0, entityType);
stmt.setUUID(1, entityId);
stmt.setString(0, entityId.getEntityType().name());
stmt.setUUID(1, entityId.getId());
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);
}
@Override
public ResultSetFuture findAllLatest(String entityType, UUID entityId) {
public ResultSetFuture findAllLatest(EntityId entityId) {
BoundStatement stmt = getFindAllLatestStmt().bind();
stmt.setString(0, entityType);
stmt.setUUID(1, entityId);
log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityType, entityId);
stmt.setString(0, entityId.getEntityType().name());
stmt.setUUID(1, entityId.getId());
log.debug("Generated query [{}] for entityType {} and entityId {}", stmt, entityId.getEntityType(), entityId.getId());
return executeAsyncRead(stmt);
}
@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();
BoundStatement stmt = getSaveStmt(type).bind()
.setString(0, entityType)
.setUUID(1, entityId)
.setString(0, entityId.getEntityType().name())
.setUUID(1, entityId.getId())
.setString(2, tsKvEntry.getKey())
.setLong(3, partition)
.setLong(4, tsKvEntry.getTs());
@ -267,11 +268,11 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
}
@Override
public ResultSetFuture saveLatest(String entityType, UUID entityId, TsKvEntry tsKvEntry) {
public ResultSetFuture saveLatest(EntityId entityId, TsKvEntry tsKvEntry) {
DataType type = tsKvEntry.getDataType();
BoundStatement stmt = getLatestStmt(type).bind()
.setString(0, entityType)
.setUUID(1, entityId)
.setString(0, entityId.getEntityType().name())
.setUUID(1, entityId.getId())
.setString(2, tsKvEntry.getKey())
.setLong(3, tsKvEntry.getTs());
addValue(tsKvEntry, stmt, 4);
@ -279,11 +280,11 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
}
@Override
public ResultSetFuture savePartition(String entityType, UUID entityId, long partition, String key) {
log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityType, entityId, key);
public ResultSetFuture savePartition(EntityId entityId, long partition, String key) {
log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityId.getEntityType(), entityId.getId(), key);
return executeAsyncWrite(getPartitionInsertStmt().bind()
.setString(0, entityType)
.setUUID(1, entityId)
.setString(0, entityId.getEntityType().name())
.setUUID(1, entityId.getId())
.setLong(2, partition)
.setString(3, key));
}
@ -339,9 +340,9 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
* Select existing partitions from the table
* <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) {
Select.Where select = QueryBuilder.select(ModelConstants.PARTITION_COLUMN).from(ModelConstants.TS_KV_PARTITIONS_CF).where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityType))
.and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId)).and(eq(ModelConstants.KEY_COLUMN, key));
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, entityId.getEntityType().name()))
.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.lte(ModelConstants.PARTITION_COLUMN, maxPartition));
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.ListenableFuture;
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.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -59,30 +60,30 @@ public class BaseTimeseriesService implements TimeseriesService {
private TimeseriesDao timeseriesDao;
@Override
public ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries) {
validate(entityType, entityId);
public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries) {
validate(entityId);
queries.forEach(query -> validate(query));
return timeseriesDao.findAllAsync(entityType, entityId.getId(), queries);
return timeseriesDao.findAllAsync(entityId, queries);
}
@Override
public ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys) {
validate(entityType, entityId);
public ListenableFuture<List<ResultSet>> findLatest(EntityId entityId, Collection<String> keys) {
validate(entityId);
List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(keys.size());
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);
}
@Override
public ResultSetFuture findAllLatest(String entityType, UUIDBased entityId) {
validate(entityType, entityId);
return timeseriesDao.findAllLatest(entityType, entityId.getId());
public ResultSetFuture findAllLatest(EntityId entityId) {
validate(entityId);
return timeseriesDao.findAllLatest(entityId);
}
@Override
public ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, TsKvEntry tsKvEntry) {
validate(entityType, entityId);
public ListenableFuture<List<ResultSet>> save(EntityId entityId, TsKvEntry tsKvEntry) {
validate(entityId);
if (tsKvEntry == 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());
List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY);
saveAndRegisterFutures(futures, entityType, tsKvEntry, uid, partitionTs);
saveAndRegisterFutures(futures, entityId, tsKvEntry, partitionTs);
return Futures.allAsList(futures);
}
@Override
public ListenableFuture<List<ResultSet>> save(String entityType, UUIDBased entityId, List<TsKvEntry> tsKvEntries) {
validate(entityType, entityId);
public ListenableFuture<List<ResultSet>> save(EntityId entityId, List<TsKvEntry> tsKvEntries) {
validate(entityId);
List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY);
for (TsKvEntry tsKvEntry : tsKvEntries) {
if (tsKvEntry == null) {
@ -104,7 +105,7 @@ public class BaseTimeseriesService implements TimeseriesService {
}
UUID uid = entityId.getId();
long partitionTs = timeseriesDao.toPartitionTs(tsKvEntry.getTs());
saveAndRegisterFutures(futures, entityType, tsKvEntry, uid, partitionTs);
saveAndRegisterFutures(futures, entityId, tsKvEntry, partitionTs);
}
return Futures.allAsList(futures);
}
@ -119,15 +120,14 @@ public class BaseTimeseriesService implements TimeseriesService {
return timeseriesDao.convertResultToTsKvEntryList(rs.all());
}
private void saveAndRegisterFutures(List<ResultSetFuture> futures, String entityType, TsKvEntry tsKvEntry, UUID uid, long partitionTs) {
futures.add(timeseriesDao.savePartition(entityType, uid, partitionTs, tsKvEntry.getKey()));
futures.add(timeseriesDao.saveLatest(entityType, uid, tsKvEntry));
futures.add(timeseriesDao.save(entityType, uid, partitionTs, tsKvEntry));
private void saveAndRegisterFutures(List<ResultSetFuture> futures, EntityId entityId, TsKvEntry tsKvEntry, long partitionTs) {
futures.add(timeseriesDao.savePartition(entityId, partitionTs, tsKvEntry.getKey()));
futures.add(timeseriesDao.saveLatest(entityId, tsKvEntry));
futures.add(timeseriesDao.save(entityId, partitionTs, tsKvEntry));
}
private static void validate(String entityType, UUIDBased entityId) {
Validator.validateString(entityType, "Incorrect entityType " + entityType);
Validator.validateId(entityId, "Incorrect entityId " + entityId);
private static void validate(EntityId entityId) {
Validator.validateEntityId(entityId, "Incorrect entityId " + entityId);
}
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.Row;
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.TsKvQuery;
@ -33,17 +34,17 @@ public interface TimeseriesDao {
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);

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.google.common.util.concurrent.ListenableFuture;
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.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
@ -33,15 +34,15 @@ import java.util.Set;
*/
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);

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.UserCredentials;
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.IncorrectParameterException;
import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
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.stereotype.Service;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
public class UserServiceImpl extends BaseEntityService implements UserService {
@Autowired
private UserDao userDao;
@ -169,6 +168,7 @@ public class UserServiceImpl implements UserService {
validateId(userId, "Incorrect userId " + userId);
UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByUserId(userId.getId());
userCredentialsDao.removeById(userCredentialsEntity.getId());
deleteEntityRelations(userId);
userDao.removeById(userId.getId());
}

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

@ -202,6 +202,73 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_credent
WHERE credentials_id IS NOT NULL AND id IS NOT NULL
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.alarm (
id timeuuid,
tenant_id timeuuid,
type text,
originator_id timeuuid,
originator_type text,
severity text,
status text,
start_ts bigint,
end_ts bigint,
ack_ts bigint,
clear_ts bigint,
details text,
propagate boolean,
PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id)
);
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 (
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.event.EventService;
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.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantService;
@ -110,6 +111,9 @@ public abstract class AbstractServiceTest {
@Autowired
protected EventService eventService;
@Autowired
protected RelationService relationService;
@Autowired
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);
ResultSetFuture rsFuture = tsService.findAllLatest(DataConstants.DEVICE, deviceId);
ResultSetFuture rsFuture = tsService.findAllLatest(deviceId);
List<TsKvEntry> tsList = tsService.convertResultSetToTsKvEntryList(rsFuture.get());
assertNotNull(tsList);
@ -95,7 +95,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
saveEntries(deviceId, TS - 1);
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(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, 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();
assertEquals(3, list.size());
assertEquals(55000, list.get(0).getTs());
@ -126,7 +126,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(35000, list.get(2).getTs());
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();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
@ -138,7 +138,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
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();
assertEquals(3, list.size());
@ -151,7 +151,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
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();
assertEquals(3, list.size());
@ -164,7 +164,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
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();
assertEquals(3, list.size());
@ -177,7 +177,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
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();
assertEquals(3, list.size());
@ -193,15 +193,15 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception {
TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value));
tsService.save(DataConstants.DEVICE, deviceId, entry).get();
tsService.save(deviceId, entry).get();
return entry;
}
private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException {
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, stringKvEntry)).get();
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, longKvEntry)).get();
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, doubleKvEntry)).get();
tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, booleanKvEntry)).get();
tsService.save(deviceId, toTsEntry(ts, stringKvEntry)).get();
tsService.save(deviceId, toTsEntry(ts, longKvEntry)).get();
tsService.save(deviceId, toTsEntry(ts, doubleKvEntry)).get();
tsService.save(deviceId, toTsEntry(ts, booleanKvEntry)).get();
}
private static TsKvEntry toTsEntry(long ts, KvEntry entry) {

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.
*/
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
*/
Optional<ServerAddress> resolve(DeviceId deviceId);
Optional<ServerAddress> resolve(EntityId entityId);
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
*/
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);
}

5
extensions-core/pom.xml

@ -41,6 +41,11 @@
<artifactId>extensions-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>transport</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<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 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.kv.*;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@ -39,7 +40,7 @@ import java.util.function.Function;
@Slf4j
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<>();
@ -48,28 +49,28 @@ public class SubscriptionManager {
@Setter
private TelemetryRpcMsgHandler rpcHandler;
public void addLocalWsSubscription(PluginContext ctx, String sessionId, DeviceId deviceId, SubscriptionState sub) {
Optional<ServerAddress> server = ctx.resolve(deviceId);
public void addLocalWsSubscription(PluginContext ctx, String sessionId, EntityId entityId, SubscriptionState sub) {
Optional<ServerAddress> server = ctx.resolve(entityId);
Subscription subscription;
if (server.isPresent()) {
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);
rpcHandler.onNewSubscription(ctx, address, sessionId, subscription);
} 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);
}
registerSubscription(sessionId, deviceId, subscription);
registerSubscription(sessionId, entityId, subscription);
}
public void addRemoteWsSubscription(PluginContext ctx, ServerAddress address, String sessionId, Subscription subscription) {
DeviceId deviceId = subscription.getDeviceId();
log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), deviceId, address);
registerSubscription(sessionId, deviceId, subscription);
EntityId entityId = subscription.getEntityId();
log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address);
registerSubscription(sessionId, entityId, subscription);
if (subscription.getType() == SubscriptionType.ATTRIBUTES) {
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
public void onSuccess(PluginContext ctx, List<AttributeKvEntry> values) {
List<TsKvEntry> missedUpdates = new ArrayList<>();
@ -95,7 +96,7 @@ public class SubscriptionManager {
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
public void onSuccess(PluginContext ctx, List<TsKvEntry> missedUpdates) {
if (!missedUpdates.isEmpty()) {
@ -112,11 +113,11 @@ public class SubscriptionManager {
}
private void registerSubscription(String sessionId, DeviceId deviceId, Subscription subscription) {
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(subscription.getDeviceId());
private void registerSubscription(String sessionId, EntityId entityId, Subscription subscription) {
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(subscription.getEntityId());
if (deviceSubscriptions == null) {
deviceSubscriptions = new HashSet<>();
subscriptionsByDeviceId.put(deviceId, deviceSubscriptions);
subscriptionsByEntityId.put(entityId, deviceSubscriptions);
}
deviceSubscriptions.add(subscription);
Map<Integer, Subscription> sessionSubscriptions = subscriptionsByWsSessionId.get(sessionId);
@ -133,7 +134,7 @@ public class SubscriptionManager {
if (sessionSubscriptions != null) {
Subscription subscription = sessionSubscriptions.remove(subscriptionId);
if (subscription != null) {
DeviceId deviceId = subscription.getDeviceId();
EntityId entityId = subscription.getEntityId();
if (subscription.isLocal() && subscription.getServer() != null) {
rpcHandler.onSubscriptionClose(ctx, subscription.getServer(), sessionId, subscription.getSubscriptionId());
}
@ -143,13 +144,13 @@ public class SubscriptionManager {
} else {
log.debug("[{}] Removed session subscription.", sessionId);
}
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(deviceId);
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) {
boolean result = deviceSubscriptions.remove(subscription);
if (result) {
if (deviceSubscriptions.size() == 0) {
log.debug("[{}] Removed last subscription for particular device.", sessionId);
subscriptionsByDeviceId.remove(deviceId);
subscriptionsByEntityId.remove(entityId);
} else {
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) {
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(deviceId);
public void onLocalSubscriptionUpdate(PluginContext ctx, EntityId entityId, SubscriptionType type, Function<Subscription, List<TsKvEntry>> f) {
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) {
deviceSubscriptions.stream().filter(s -> type == s.getType()).forEach(s -> {
String sessionId = s.getWsSessionId();
@ -184,7 +185,7 @@ public class SubscriptionManager {
}
});
} 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) {
Optional<ServerAddress> serverAddress = ctx.resolve(deviceId);
public void onAttributesUpdateFromServer(PluginContext ctx, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
Optional<ServerAddress> serverAddress = ctx.resolve(entityId);
if (!serverAddress.isPresent()) {
onLocalSubscriptionUpdate(ctx, deviceId, SubscriptionType.ATTRIBUTES, s -> {
onLocalSubscriptionUpdate(ctx, entityId, SubscriptionType.ATTRIBUTES, s -> {
List<TsKvEntry> subscriptionUpdate = new ArrayList<TsKvEntry>();
for (AttributeKvEntry kv : attributes) {
if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
@ -210,7 +211,24 @@ public class SubscriptionManager {
return subscriptionUpdate;
});
} 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();
for (Subscription subscription : sessionSubscriptions.values()) {
DeviceId deviceId = subscription.getDeviceId();
Set<Subscription> deviceSubscriptions = subscriptionsByDeviceId.get(deviceId);
EntityId entityId = subscription.getEntityId();
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
deviceSubscriptions.remove(subscription);
if (deviceSubscriptions.isEmpty()) {
subscriptionsByDeviceId.remove(deviceId);
subscriptionsByEntityId.remove(entityId);
}
}
subscriptionsByWsSessionId.remove(sessionId);
@ -272,9 +290,9 @@ public class SubscriptionManager {
public void onClusterUpdate(PluginContext ctx) {
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()) {
Map.Entry<DeviceId, Set<Subscription>> e = deviceIterator.next();
Map.Entry<EntityId, Set<Subscription>> e = deviceIterator.next();
Set<Subscription> subscriptions = e.getValue();
Optional<ServerAddress> newAddressOptional = ctx.resolve(e.getKey());
if (newAddressOptional.isPresent()) {
@ -317,6 +335,6 @@ public class SubscriptionManager {
public void 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 {
private int cmdId;
private String deviceId;
private String entityType;
private String entityId;
private String keys;
private long startTs;
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 {
private int cmdId;
private String deviceId;
private String entityType;
private String entityId;
private String keys;
private String scope;
private boolean unsubscribe;
@ -35,7 +36,7 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd {
@Override
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;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
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.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
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.PluginContext;
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 {
RestRequest request = msg.getRequest();
String[] pathParams = request.getPathParams();
if (pathParams.length >= 3) {
String deviceIdStr = pathParams[0];
String method = pathParams[1];
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;
}
if (pathParams.length < 4) {
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")) {
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));
}
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
@Override
public void onFailure(PluginContext ctx, Exception e) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
});
} else if (entity.equals("attributes")) {
PluginCallback<List<AttributeKvEntry>> callback = getAttributeKeysPluginCallback(msg);
if (!StringUtils.isEmpty(scope)) {
ctx.loadAttributes(deviceId, scope, callback);
} else {
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
if (method.equals("keys")) {
if (feature == TelemetryFeature.TIMESERIES) {
ctx.loadLatestTimeseries(entityId, 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
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)) {
String keysStr = request.getParameter("keys");
List<String> keys = Arrays.asList(keysStr.split(","));
}
} else if (method.equals("values")) {
if (feature == TelemetryFeature.TIMESERIES) {
String keysStr = request.getParameter("keys");
List<String> keys = Arrays.asList(keysStr.split(","));
Optional<Long> startTs = request.getLongParamValue("startTs");
Optional<Long> endTs = request.getLongParamValue("endTs");
Optional<Long> interval = request.getLongParamValue("interval");
Optional<Integer> limit = request.getIntParamValue("limit");
Optional<Long> startTs = request.getLongParamValue("startTs");
Optional<Long> endTs = request.getLongParamValue("endTs");
Optional<Long> interval = request.getLongParamValue("interval");
Optional<Integer> limit = request.getIntParamValue("limit");
if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) {
if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent()) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return;
}
Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name()));
if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) {
if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent()) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return;
}
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))
.collect(Collectors.toList());
ctx.loadTimeseries(deviceId, queries, getTsKvListCallback(msg));
PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg);
if (!StringUtils.isEmpty(scope)) {
if (!StringUtils.isEmpty(keys)) {
List<String> keyList = Arrays.asList(keys.split(","));
ctx.loadAttributes(entityId, scope, keyList, callback);
} else {
ctx.loadLatestTimeseries(deviceId, keys, getTsKvListCallback(msg));
ctx.loadAttributes(entityId, scope, callback);
}
} else if ("attributes".equals(entity)) {
String keys = request.getParameter("keys", "");
PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg);
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 {
if (!StringUtils.isEmpty(keys)) {
List<String> keyList = Arrays.asList(keys.split(","));
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), keyList, callback);
} else {
if (!StringUtils.isEmpty(keys)) {
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);
}
ctx.loadAttributes(entityId, 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
public void handleHttpPostRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException {
RestRequest request = msg.getRequest();
try {
String[] pathParams = request.getPathParams();
EntityId entityId;
String scope;
TelemetryFeature feature;
if (pathParams.length == 2) {
DeviceId deviceId = DeviceId.fromString(pathParams[0]);
String scope = pathParams[1];
entityId = DeviceId.fromString(pathParams[0]);
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) ||
DataConstants.SHARED_SCOPE.equals(scope)) {
JsonNode jsonNode = jsonMapper.readTree(request.getRequestBody());
@ -185,11 +186,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
}
});
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
public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
subscriptionManager.onAttributesUpdateFromServer(ctx, deviceId, scope, attributes);
subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes);
}
@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) {
log.debug("Failed to process POST request due to exception", e);
@ -214,29 +237,38 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
RestRequest request = msg.getRequest();
try {
String[] pathParams = request.getPathParams();
EntityId entityId;
String scope;
if (pathParams.length == 2) {
DeviceId deviceId = DeviceId.fromString(pathParams[0]);
String scope = pathParams[1];
if (DataConstants.SERVER_SCOPE.equals(scope) ||
DataConstants.SHARED_SCOPE.equals(scope) ||
DataConstants.CLIENT_SCOPE.equals(scope)) {
String keysParam = request.getParameter("keys");
if (!StringUtils.isEmpty(keysParam)) {
String[] keys = keysParam.split(",");
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));
}
entityId = DeviceId.fromString(pathParams[0]);
scope = pathParams[1];
} else if (pathParams.length == 3) {
entityId = EntityIdFactory.getByTypeAndId(pathParams[0], pathParams[1]);
scope = pathParams[2];
} else {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
return;
}
@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;
}
if (DataConstants.SERVER_SCOPE.equals(scope) ||
DataConstants.SHARED_SCOPE.equals(scope) ||
DataConstants.CLIENT_SCOPE.equals(scope)) {
String keysParam = request.getParameter("keys");
if (!StringUtils.isEmpty(keysParam)) {
String[] keys = keysParam.split(",");
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) {
@ -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 lombok.RequiredArgsConstructor;
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.msg.cluster.ServerAddress;
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 ATTRIBUTES_UPDATE_CLAZZ = 2;
private static final int SUBSCRIPTION_UPDATE_CLAZZ = 3;
private static final int SESSION_CLOSE_CLAZZ = 4;
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 5;
private static final int TIMESERIES_UPDATE_CLAZZ = 3;
private static final int SUBSCRIPTION_UPDATE_CLAZZ = 4;
private static final int SESSION_CLOSE_CLAZZ = 5;
private static final int SUBSCRIPTION_CLOSE_CLAZZ = 6;
@Override
public void process(PluginContext ctx, RpcMsg msg) {
@ -60,6 +62,9 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
case ATTRIBUTES_UPDATE_CLAZZ:
processAttributeUpdate(ctx, msg);
break;
case TIMESERIES_UPDATE_CLAZZ:
processTimeseriesUpdate(ctx, msg);
break;
case SESSION_CLOSE_CLAZZ:
processSessionClose(ctx, msg);
break;
@ -88,10 +93,21 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
} catch (InvalidProtocolBufferException 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()));
}
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) {
SubscriptionProto proto;
try {
@ -101,7 +117,7 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
}
Map<String, Long> statesMap = proto.getKeyStatesList().stream().collect(Collectors.toMap(SubscriptionKetStateProto::getKey, SubscriptionKetStateProto::getTs));
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());
subscriptionManager.addRemoteWsSubscription(ctx, msg.getServerAddress(), proto.getSessionId(), subscription);
}
@ -110,7 +126,8 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
SubscriptionProto.Builder builder = SubscriptionProto.newBuilder();
builder.setSessionId(sessionId);
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.setAllKeys(cmd.isAllKeys());
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) {
ctx.sendPluginRpcMsg(new RpcMsg(address, ATTRIBUTES_UPDATE_CLAZZ, getAttributesUpdateProto(deviceId, scope, attributes).toByteArray()));
public void onAttributesUpdate(PluginContext ctx, ServerAddress address, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
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();
builder.setDeviceId(deviceId.toString());
builder.setEntityId(entityId.getId().toString());
builder.setEntityType(entityId.getEntityType().name());
builder.setScope(scope);
attributes.forEach(
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());
}
);
attributes.forEach(attr -> builder.addData(toKeyValueProto(attr.getLastUpdateTs(), attr).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;
DataType type = DataType.values()[proto.getValueType()];
switch (type) {
@ -246,7 +284,7 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
entry = new StringDataEntry(proto.getKey(), proto.getStrValue());
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 org.springframework.util.StringUtils;
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.extensions.api.exception.UnauthorizedException;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@ -101,8 +102,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
if (cmd.isUnsubscribe()) {
unsubscribe(ctx, cmd, sessionId);
} else if (validateSubscriptionCmd(ctx, sessionRef, cmd)) {
log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), cmd.getDeviceId());
DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId());
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId);
Optional<Set<String>> keysOptional = getKeys(cmd);
SubscriptionState sub;
if (keysOptional.isPresent()) {
@ -118,8 +119,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
keys.forEach(key -> subState.put(key, 0L));
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, false, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
}
@Override
@ -138,9 +139,9 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
};
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 {
ctx.loadAttributes(deviceId, cmd.getScope(), keys, callback);
ctx.loadAttributes(entityId, cmd.getScope(), keys, callback);
}
} else {
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());
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, true, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, true, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
}
@Override
@ -166,9 +167,9 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
};
if (StringUtils.isEmpty(cmd.getScope())) {
ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
} 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()) {
unsubscribe(ctx, cmd, sessionId);
} 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);
if (keysOptional.isPresent()) {
long startTs;
if (cmd.getTimeWindow() > 0) {
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();
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());
ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
ctx.loadTimeseries(entityId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys));
} else {
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
startTs = System.currentTimeMillis();
log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), cmd.getDeviceId());
ctx.loadLatestTimeseries(deviceId, keys, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), entityId);
ctx.loadLatestTimeseries(entityId, keys, getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys));
}
} else {
ctx.loadLatestTimeseries(deviceId, new PluginCallback<List<TsKvEntry>>() {
ctx.loadLatestTimeseries(entityId, new PluginCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
Map<String, Long> subState = new HashMap<>(data.size());
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, true, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.TIMESERIES, true, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
}
@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>>() {
@Override
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());
keys.forEach(key -> subState.put(key, startTs));
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.TIMESERIES, false, subState);
subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
}
@Override
@ -263,7 +264,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
sendWsMsg(ctx, sessionRef, update);
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,
"Device id is empty!");
sendWsMsg(ctx, sessionRef, update);
@ -275,10 +276,11 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
sendWsMsg(ctx, sessionRef, update);
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<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
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());
ctx.loadTimeseries(entityId, queries, new PluginCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> 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) {
if (cmd.getDeviceId() == null || cmd.getDeviceId().isEmpty()) {
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
cleanupWebSocketSession(ctx, sessionId);
} else {
subscriptionManager.removeSubscription(ctx, sessionId, cmd.getCmdId());
@ -329,7 +331,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
}
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,
"Device id is empty!");
sendWsMsg(ctx, sessionRef, update);

5
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/Subscription.java

@ -18,6 +18,7 @@ package org.thingsboard.server.extensions.core.plugin.telemetry.sub;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import java.util.Map;
@ -42,8 +43,8 @@ public class Subscription {
return getSub().getSubscriptionId();
}
public DeviceId getDeviceId() {
return getSub().getDeviceId();
public EntityId getEntityId() {
return getSub().getEntityId();
}
public SubscriptionType getType() {

11
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionState.java

@ -16,9 +16,8 @@
package org.thingsboard.server.extensions.core.plugin.telemetry.sub;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
@ -30,7 +29,7 @@ public class SubscriptionState {
@Getter private final String wsSessionId;
@Getter private final int subscriptionId;
@Getter private final DeviceId deviceId;
@Getter private final EntityId entityId;
@Getter private final SubscriptionType type;
@Getter private final boolean allKeys;
@Getter private final Map<String, Long> keyStates;
@ -44,7 +43,7 @@ public class SubscriptionState {
if (subscriptionId != that.subscriptionId) return false;
if (wsSessionId != null ? !wsSessionId.equals(that.wsSessionId) : that.wsSessionId != null) return false;
if (deviceId != null ? !deviceId.equals(that.deviceId) : that.deviceId != null) return false;
if (entityId != null ? !entityId.equals(that.entityId) : that.entityId != null) return false;
return type == that.type;
}
@ -52,7 +51,7 @@ public class SubscriptionState {
public int hashCode() {
int result = wsSessionId != null ? wsSessionId.hashCode() : 0;
result = 31 * result + subscriptionId;
result = 31 * result + (deviceId != null ? deviceId.hashCode() : 0);
result = 31 * result + (entityId != null ? entityId.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
@ -61,7 +60,7 @@ public class SubscriptionState {
public String toString() {
return "SubscriptionState{" +
"type=" + type +
", deviceId=" + deviceId +
", entityId=" + entityId +
", subscriptionId=" + subscriptionId +
", wsSessionId='" + wsSessionId + '\'' +
'}';

24
extensions-core/src/main/proto/telemetry.proto

@ -22,10 +22,11 @@ option java_outer_classname = "TelemetryPluginProtos";
message SubscriptionProto {
string sessionId = 1;
int32 subscriptionId = 2;
string deviceId = 3;
string type = 4;
bool allKeys = 5;
repeated SubscriptionKetStateProto keyStates = 6;
string entityType = 3;
string entityId = 4;
string type = 5;
bool allKeys = 6;
repeated SubscriptionKetStateProto keyStates = 7;
}
message SubscriptionUpdateProto {
@ -37,9 +38,16 @@ message SubscriptionUpdateProto {
}
message AttributeUpdateProto {
string deviceId = 1;
string scope = 2;
repeated AttributeUpdateValueListProto data = 3;
string entityType = 1;
string entityId = 2;
string scope = 3;
repeated KeyValueProto data = 4;
}
message TimeseriesUpdateProto {
string entityType = 1;
string entityId = 2;
repeated KeyValueProto data = 4;
}
message SessionCloseProto {
@ -62,7 +70,7 @@ message SubscriptionUpdateValueListProto {
repeated string value = 3;
}
message AttributeUpdateValueListProto {
message KeyValueProto {
string key = 1;
int64 ts = 2;
int32 valueType = 3;

7
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java

@ -82,6 +82,7 @@ public class GatewaySessionCtx {
});
GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device);
devices.put(deviceName, ctx);
log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName);
processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg())));
processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg())));
}
@ -94,6 +95,9 @@ public class GatewaySessionCtx {
if (deviceSessionCtx != null) {
processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId()));
deviceSessionCtx.setClosed(true);
log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName);
} else {
log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName);
}
ack(msg);
}
@ -191,7 +195,8 @@ public class GatewaySessionCtx {
private String checkDeviceConnected(String deviceName) {
if (!devices.containsKey(deviceName)) {
throw new RuntimeException("Device is not connected!");
log.debug("[{}] Missing device [{}] for the gateway session", gatewaySessionId, deviceName);
throw new RuntimeException("Device " + deviceName + " is not connected!");
} else {
return deviceName;
}

261
ui/src/app/api/asset.service.js

@ -0,0 +1,261 @@
/*
* 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.
*/
export default angular.module('thingsboard.api.asset', [])
.factory('assetService', AssetService)
.name;
/*@ngInject*/
function AssetService($http, $q, customerService, userService) {
var service = {
getAsset: getAsset,
getAssets: getAssets,
saveAsset: saveAsset,
deleteAsset: deleteAsset,
assignAssetToCustomer: assignAssetToCustomer,
unassignAssetFromCustomer: unassignAssetFromCustomer,
makeAssetPublic: makeAssetPublic,
getTenantAssets: getTenantAssets,
getCustomerAssets: getCustomerAssets,
findByQuery: findByQuery,
fetchAssetsByNameFilter: fetchAssetsByNameFilter
}
return service;
function getAsset(assetId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/asset/' + assetId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getAssets(assetIds, config) {
var deferred = $q.defer();
var ids = '';
for (var i=0;i<assetIds.length;i++) {
if (i>0) {
ids += ',';
}
ids += assetIds[i];
}
var url = '/api/assets?assetIds=' + ids;
$http.get(url, config).then(function success(response) {
var assets = response.data;
assets.sort(function (asset1, asset2) {
var id1 = asset1.id.id;
var id2 = asset2.id.id;
var index1 = assetIds.indexOf(id1);
var index2 = assetIds.indexOf(id2);
return index1 - index2;
});
deferred.resolve(assets);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function saveAsset(asset, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/asset';
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.post(url, asset, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function deleteAsset(assetId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/asset/' + assetId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.delete(url, config).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function assignAssetToCustomer(customerId, assetId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/asset/' + assetId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.post(url, null, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function unassignAssetFromCustomer(assetId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/customer/asset/' + assetId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.delete(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function makeAssetPublic(assetId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/customer/public/asset/' + assetId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.post(url, null, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getTenantAssets(pageLink, applyCustomersInfo, config) {
var deferred = $q.defer();
var url = '/api/tenant/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
function success(data) {
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
}
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
function success(data) {
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
}
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByQuery(query, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/assets';
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.post(url, query, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function fetchAssetsByNameFilter(assetNameFilter, limit, applyCustomersInfo, config) {
var deferred = $q.defer();
var user = userService.getCurrentUser();
var promise;
var pageLink = {limit: limit, textSearch: assetNameFilter};
if (user.authority === 'CUSTOMER_USER') {
var customerId = user.customerId;
promise = getCustomerAssets(customerId, pageLink, applyCustomersInfo, config);
} else {
promise = getTenantAssets(pageLink, applyCustomersInfo, config);
}
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
} else {
deferred.resolve(null);
}
},
function fail() {
deferred.resolve(null);
}
);
return deferred.promise;
}
}

234
ui/src/app/api/attribute.service.js

@ -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.
*/
export default angular.module('thingsboard.api.attribute', [])
.factory('attributeService', AttributeService)
.name;
/*@ngInject*/
function AttributeService($http, $q, $filter, types, telemetryWebsocketService) {
var entityAttributesSubscriptionMap = {};
var service = {
getEntityKeys: getEntityKeys,
getEntityTimeseriesValues: getEntityTimeseriesValues,
getEntityAttributes: getEntityAttributes,
subscribeForEntityAttributes: subscribeForEntityAttributes,
unsubscribeForEntityAttributes: unsubscribeForEntityAttributes,
saveEntityAttributes: saveEntityAttributes,
deleteEntityAttributes: deleteEntityAttributes
}
return service;
function getEntityKeys(entityType, entityId, query, type) {
var deferred = $q.defer();
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/';
if (type === types.dataKeyType.timeseries) {
url += 'timeseries';
} else if (type === types.dataKeyType.attribute) {
url += 'attributes';
}
$http.get(url, null).then(function success(response) {
var result = [];
if (response.data) {
if (query) {
var dataKeys = response.data;
var lowercaseQuery = angular.lowercase(query);
for (var i=0; i<dataKeys.length;i++) {
if (angular.lowercase(dataKeys[i]).indexOf(lowercaseQuery) === 0) {
result.push(dataKeys[i]);
}
}
} else {
result = response.data;
}
}
deferred.resolve(result);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function getEntityTimeseriesValues(entityType, entityId, keys, startTs, endTs, limit) {
var deferred = $q.defer();
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/values/timeseries';
url += '?keys=' + keys;
url += '&startTs=' + startTs;
url += '&endTs=' + endTs;
if (angular.isDefined(limit)) {
url += '&limit=' + limit;
}
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function processAttributes(attributes, query, deferred, successCallback, update, apply) {
attributes = $filter('orderBy')(attributes, query.order);
if (query.search != null) {
attributes = $filter('filter')(attributes, {key: query.search});
}
var responseData = {
count: attributes.length
}
var startIndex = query.limit * (query.page - 1);
responseData.data = attributes.slice(startIndex, startIndex + query.limit);
successCallback(responseData, update, apply);
if (deferred) {
deferred.resolve();
}
}
function getEntityAttributes(entityType, entityId, attributeScope, query, successCallback, config) {
var deferred = $q.defer();
var subscriptionId = entityType + entityId + attributeScope;
var eas = entityAttributesSubscriptionMap[subscriptionId];
if (eas) {
if (eas.attributes) {
processAttributes(eas.attributes, query, deferred, successCallback);
eas.subscriptionCallback = function(attributes) {
processAttributes(attributes, query, null, successCallback, true, true);
}
} else {
eas.subscriptionCallback = function(attributes) {
processAttributes(attributes, query, deferred, successCallback, false, true);
eas.subscriptionCallback = function(attributes) {
processAttributes(attributes, query, null, successCallback, true, true);
}
}
}
} else {
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/values/attributes/' + attributeScope;
$http.get(url, config).then(function success(response) {
processAttributes(response.data, query, deferred, successCallback);
}, function fail() {
deferred.reject();
});
}
return deferred;
}
function onSubscriptionData(data, subscriptionId) {
var entityAttributesSubscription = entityAttributesSubscriptionMap[subscriptionId];
if (entityAttributesSubscription) {
if (!entityAttributesSubscription.attributes) {
entityAttributesSubscription.attributes = [];
entityAttributesSubscription.keys = {};
}
var attributes = entityAttributesSubscription.attributes;
var keys = entityAttributesSubscription.keys;
for (var key in data) {
var index = keys[key];
var attribute;
if (index > -1) {
attribute = attributes[index];
} else {
attribute = {
key: key
};
index = attributes.push(attribute)-1;
keys[key] = index;
}
var attrData = data[key][0];
attribute.lastUpdateTs = attrData[0];
attribute.value = attrData[1];
}
if (entityAttributesSubscription.subscriptionCallback) {
entityAttributesSubscription.subscriptionCallback(attributes);
}
}
}
function subscribeForEntityAttributes(entityType, entityId, attributeScope) {
var subscriptionId = entityType + entityId + attributeScope;
var entityAttributesSubscription = entityAttributesSubscriptionMap[subscriptionId];
if (!entityAttributesSubscription) {
var subscriptionCommand = {
entityType: entityType,
entityId: entityId,
scope: attributeScope
};
var type = attributeScope === types.latestTelemetry.value ?
types.dataKeyType.timeseries : types.dataKeyType.attribute;
var subscriber = {
subscriptionCommand: subscriptionCommand,
type: type,
onData: function (data) {
if (data.data) {
onSubscriptionData(data.data, subscriptionId);
}
}
};
entityAttributesSubscription = {
subscriber: subscriber,
attributes: null
};
entityAttributesSubscriptionMap[subscriptionId] = entityAttributesSubscription;
telemetryWebsocketService.subscribe(subscriber);
}
return subscriptionId;
}
function unsubscribeForEntityAttributes(subscriptionId) {
var entityAttributesSubscription = entityAttributesSubscriptionMap[subscriptionId];
if (entityAttributesSubscription) {
telemetryWebsocketService.unsubscribe(entityAttributesSubscription.subscriber);
delete entityAttributesSubscriptionMap[subscriptionId];
}
}
function saveEntityAttributes(entityType, entityId, attributeScope, attributes) {
var deferred = $q.defer();
var attributesData = {};
for (var a=0; a<attributes.length;a++) {
attributesData[attributes[a].key] = attributes[a].value;
}
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope;
$http.post(url, attributesData).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function deleteEntityAttributes(entityType, entityId, attributeScope, attributes) {
var deferred = $q.defer();
var keys = '';
for (var i = 0; i < attributes.length; i++) {
if (i > 0) {
keys += ',';
}
keys += attributes[i].key;
}
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope + '?keys=' + keys;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

25
ui/src/app/api/datasource.service.js

@ -35,11 +35,10 @@ function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, t
return service;
function subscribeToDatasource(listener) {
var datasource = listener.datasource;
if (datasource.type === types.datasourceType.device && !listener.deviceId) {
if (datasource.type === types.datasourceType.entity && (!listener.entityId || !listener.entityType)) {
return;
}
@ -64,8 +63,9 @@ function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, t
if (listener.subscriptionType === types.widgetType.timeseries.value) {
datasourceSubscription.subscriptionTimewindow = angular.copy(listener.subscriptionTimewindow);
}
if (datasourceSubscription.datasourceType === types.datasourceType.device) {
datasourceSubscription.deviceId = listener.deviceId;
if (datasourceSubscription.datasourceType === types.datasourceType.entity) {
datasourceSubscription.entityType = listener.entityType;
datasourceSubscription.entityId = listener.entityId;
}
listener.datasourceSubscriptionKey = utils.objectHashCode(datasourceSubscription);
@ -141,7 +141,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
dataKey.postFunc = new Function("time", "value", "prevValue", dataKey.postFuncBody);
}
}
if (datasourceType === types.datasourceType.device || datasourceSubscription.type === types.widgetType.timeseries.value) {
if (datasourceType === types.datasourceType.entity || datasourceSubscription.type === types.widgetType.timeseries.value) {
if (datasourceType === types.datasourceType.function) {
key = dataKey.name + '_' + dataKey.index + '_' + dataKey.type;
} else {
@ -191,7 +191,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
function syncListener(listener) {
var key;
var dataKey;
if (datasourceType === types.datasourceType.device || datasourceSubscription.type === types.widgetType.timeseries.value) {
if (datasourceType === types.datasourceType.entity || datasourceSubscription.type === types.widgetType.timeseries.value) {
for (key in dataKeys) {
var dataKeysList = dataKeys[key];
for (var i = 0; i < dataKeysList.length; i++) {
@ -220,7 +220,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
var tsKeyNames = [];
var dataKey;
if (datasourceType === types.datasourceType.device) {
if (datasourceType === types.datasourceType.entity) {
//send subscribe command
@ -252,7 +252,8 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
if (history) {
var historyCommand = {
deviceId: datasourceSubscription.deviceId,
entityType: datasourceSubscription.entityType,
entityId: datasourceSubscription.entityId,
keys: tsKeys,
startTs: subsTw.fixedWindow.startTimeMs,
endTs: subsTw.fixedWindow.endTimeMs,
@ -282,7 +283,8 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
} else {
subscriptionCommand = {
deviceId: datasourceSubscription.deviceId,
entityType: datasourceSubscription.entityType,
entityId: datasourceSubscription.entityId,
keys: tsKeys
};
@ -328,7 +330,8 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
if (attrKeys.length > 0) {
subscriptionCommand = {
deviceId: datasourceSubscription.deviceId,
entityType: datasourceSubscription.entityType,
entityId: datasourceSubscription.entityId,
keys: attrKeys
};
@ -404,7 +407,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
$timeout.cancel(timer);
timer = null;
}
if (datasourceType === types.datasourceType.device) {
if (datasourceType === types.datasourceType.entity) {
for (var cmdId in subscribers) {
var subscriber = subscribers[cmdId];
telemetryWebsocketService.unsubscribe(subscriber);

364
ui/src/app/api/device.service.js

@ -20,10 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes])
.name;
/*@ngInject*/
function DeviceService($http, $q, $filter, userService, customerService, telemetryWebsocketService, types) {
var deviceAttributesSubscriptionMap = {};
function DeviceService($http, $q, attributeService, customerService, types) {
var service = {
assignDeviceToCustomer: assignDeviceToCustomer,
@ -31,12 +28,7 @@ function DeviceService($http, $q, $filter, userService, customerService, telemet
getCustomerDevices: getCustomerDevices,
getDevice: getDevice,
getDevices: getDevices,
processDeviceAliases: processDeviceAliases,
checkDeviceAlias: checkDeviceAlias,
fetchAliasDeviceByNameFilter: fetchAliasDeviceByNameFilter,
getDeviceCredentials: getDeviceCredentials,
getDeviceKeys: getDeviceKeys,
getDeviceTimeseriesValues: getDeviceTimeseriesValues,
getTenantDevices: getTenantDevices,
saveDevice: saveDevice,
saveDeviceCredentials: saveDeviceCredentials,
@ -159,174 +151,6 @@ function DeviceService($http, $q, $filter, userService, customerService, telemet
return deferred.promise;
}
function fetchAliasDeviceByNameFilter(deviceNameFilter, limit, applyCustomersInfo, config) {
var deferred = $q.defer();
var user = userService.getCurrentUser();
var promise;
var pageLink = {limit: limit, textSearch: deviceNameFilter};
if (user.authority === 'CUSTOMER_USER') {
var customerId = user.customerId;
promise = getCustomerDevices(customerId, pageLink, applyCustomersInfo, config);
} else {
promise = getTenantDevices(pageLink, applyCustomersInfo, config);
}
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
} else {
deferred.resolve(null);
}
},
function fail() {
deferred.resolve(null);
}
);
return deferred.promise;
}
function deviceToDeviceInfo(device) {
return { name: device.name, id: device.id.id };
}
function devicesToDevicesInfo(devices) {
var devicesInfo = [];
for (var d = 0; d < devices.length; d++) {
devicesInfo.push(deviceToDeviceInfo(devices[d]));
}
return devicesInfo;
}
function processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred) {
if (index < aliasIds.length) {
var aliasId = aliasIds[index];
var deviceAlias = deviceAliases[aliasId];
var alias = deviceAlias.alias;
if (!deviceAlias.deviceFilter) {
getDevice(deviceAlias.deviceId).then(
function success(device) {
var resolvedAlias = {alias: alias, deviceId: device.id.id};
resolution.aliasesInfo.deviceAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.deviceAliasesInfo[aliasId] = [
deviceToDeviceInfo(device)
];
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
},
function fail() {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
}
);
} else {
var deviceFilter = deviceAlias.deviceFilter;
if (deviceFilter.useFilter) {
var deviceNameFilter = deviceFilter.deviceNameFilter;
fetchAliasDeviceByNameFilter(deviceNameFilter, 100, false).then(
function(devices) {
if (devices && devices != null) {
var resolvedAlias = {alias: alias, deviceId: devices[0].id.id};
resolution.aliasesInfo.deviceAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.deviceAliasesInfo[aliasId] = devicesToDevicesInfo(devices);
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
}
});
} else {
var deviceList = deviceFilter.deviceList;
getDevices(deviceList).then(
function success(devices) {
if (devices && devices.length > 0) {
var resolvedAlias = {alias: alias, deviceId: devices[0].id.id};
resolution.aliasesInfo.deviceAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.deviceAliasesInfo[aliasId] = devicesToDevicesInfo(devices);
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
}
},
function fail() {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
}
);
}
}
} else {
deferred.resolve(resolution);
}
}
function processDeviceAliases(deviceAliases) {
var deferred = $q.defer();
var resolution = {
aliasesInfo: {
deviceAliases: {},
deviceAliasesInfo: {}
}
};
var aliasIds = [];
if (deviceAliases) {
for (var aliasId in deviceAliases) {
aliasIds.push(aliasId);
}
}
processDeviceAlias(0, aliasIds, deviceAliases, resolution, deferred);
return deferred.promise;
}
function checkDeviceAlias(deviceAlias) {
var deferred = $q.defer();
var deviceFilter;
if (deviceAlias.deviceId) {
deviceFilter = {
useFilter: false,
deviceNameFilter: '',
deviceList: [deviceAlias.deviceId]
}
} else {
deviceFilter = deviceAlias.deviceFilter;
}
var promise;
if (deviceFilter.useFilter) {
var deviceNameFilter = deviceFilter.deviceNameFilter;
promise = fetchAliasDeviceByNameFilter(deviceNameFilter, 1, false);
} else {
var deviceList = deviceFilter.deviceList;
promise = getDevices(deviceList);
}
promise.then(
function success(devices) {
if (devices && devices.length > 0) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
},
function fail() {
deferred.resolve(false);
}
);
return deferred.promise;
}
function saveDevice(device) {
var deferred = $q.defer();
var url = '/api/device';
@ -404,198 +228,24 @@ function DeviceService($http, $q, $filter, userService, customerService, telemet
return deferred.promise;
}
function getDeviceKeys(deviceId, query, type) {
var deferred = $q.defer();
var url = '/api/plugins/telemetry/' + deviceId + '/keys/';
if (type === types.dataKeyType.timeseries) {
url += 'timeseries';
} else if (type === types.dataKeyType.attribute) {
url += 'attributes';
}
$http.get(url, null).then(function success(response) {
var result = [];
if (response.data) {
if (query) {
var dataKeys = response.data;
var lowercaseQuery = angular.lowercase(query);
for (var i=0; i<dataKeys.length;i++) {
if (angular.lowercase(dataKeys[i]).indexOf(lowercaseQuery) === 0) {
result.push(dataKeys[i]);
}
}
} else {
result = response.data;
}
}
deferred.resolve(result);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function getDeviceTimeseriesValues(deviceId, keys, startTs, endTs, limit) {
var deferred = $q.defer();
var url = '/api/plugins/telemetry/' + deviceId + '/values/timeseries';
url += '?keys=' + keys;
url += '&startTs=' + startTs;
url += '&endTs=' + endTs;
if (angular.isDefined(limit)) {
url += '&limit=' + limit;
}
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function processDeviceAttributes(attributes, query, deferred, successCallback, update, apply) {
attributes = $filter('orderBy')(attributes, query.order);
if (query.search != null) {
attributes = $filter('filter')(attributes, {key: query.search});
}
var responseData = {
count: attributes.length
}
var startIndex = query.limit * (query.page - 1);
responseData.data = attributes.slice(startIndex, startIndex + query.limit);
successCallback(responseData, update, apply);
if (deferred) {
deferred.resolve();
}
}
function getDeviceAttributes(deviceId, attributeScope, query, successCallback, config) {
var deferred = $q.defer();
var subscriptionId = deviceId + attributeScope;
var das = deviceAttributesSubscriptionMap[subscriptionId];
if (das) {
if (das.attributes) {
processDeviceAttributes(das.attributes, query, deferred, successCallback);
das.subscriptionCallback = function(attributes) {
processDeviceAttributes(attributes, query, null, successCallback, true, true);
}
} else {
das.subscriptionCallback = function(attributes) {
processDeviceAttributes(attributes, query, deferred, successCallback, false, true);
das.subscriptionCallback = function(attributes) {
processDeviceAttributes(attributes, query, null, successCallback, true, true);
}
}
}
} else {
var url = '/api/plugins/telemetry/' + deviceId + '/values/attributes/' + attributeScope;
$http.get(url, config).then(function success(response) {
processDeviceAttributes(response.data, query, deferred, successCallback);
}, function fail() {
deferred.reject();
});
}
return deferred;
}
function onSubscriptionData(data, subscriptionId) {
var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
if (deviceAttributesSubscription) {
if (!deviceAttributesSubscription.attributes) {
deviceAttributesSubscription.attributes = [];
deviceAttributesSubscription.keys = {};
}
var attributes = deviceAttributesSubscription.attributes;
var keys = deviceAttributesSubscription.keys;
for (var key in data) {
var index = keys[key];
var attribute;
if (index > -1) {
attribute = attributes[index];
} else {
attribute = {
key: key
};
index = attributes.push(attribute)-1;
keys[key] = index;
}
var attrData = data[key][0];
attribute.lastUpdateTs = attrData[0];
attribute.value = attrData[1];
}
if (deviceAttributesSubscription.subscriptionCallback) {
deviceAttributesSubscription.subscriptionCallback(attributes);
}
}
return attributeService.getEntityAttributes(types.entityType.device, deviceId, attributeScope, query, successCallback, config);
}
function subscribeForDeviceAttributes(deviceId, attributeScope) {
var subscriptionId = deviceId + attributeScope;
var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
if (!deviceAttributesSubscription) {
var subscriptionCommand = {
deviceId: deviceId,
scope: attributeScope
};
var type = attributeScope === types.latestTelemetry.value ?
types.dataKeyType.timeseries : types.dataKeyType.attribute;
var subscriber = {
subscriptionCommand: subscriptionCommand,
type: type,
onData: function (data) {
if (data.data) {
onSubscriptionData(data.data, subscriptionId);
}
}
};
deviceAttributesSubscription = {
subscriber: subscriber,
attributes: null
}
deviceAttributesSubscriptionMap[subscriptionId] = deviceAttributesSubscription;
telemetryWebsocketService.subscribe(subscriber);
}
return subscriptionId;
return attributeService.subscribeForEntityAttributes(types.entityType.device, deviceId, attributeScope);
}
function unsubscribeForDeviceAttributes(subscriptionId) {
var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
if (deviceAttributesSubscription) {
telemetryWebsocketService.unsubscribe(deviceAttributesSubscription.subscriber);
delete deviceAttributesSubscriptionMap[subscriptionId];
}
attributeService.unsubscribeForEntityAttributes(subscriptionId);
}
function saveDeviceAttributes(deviceId, attributeScope, attributes) {
var deferred = $q.defer();
var attributesData = {};
for (var a=0; a<attributes.length;a++) {
attributesData[attributes[a].key] = attributes[a].value;
}
var url = '/api/plugins/telemetry/' + deviceId + '/' + attributeScope;
$http.post(url, attributesData).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
return attributeService.saveEntityAttributes(types.entityType.device, deviceId, attributeScope, attributes);
}
function deleteDeviceAttributes(deviceId, attributeScope, attributes) {
var deferred = $q.defer();
var keys = '';
for (var i = 0; i < attributes.length; i++) {
if (i > 0) {
keys += ',';
}
keys += attributes[i].key;
}
var url = '/api/plugins/telemetry/' + deviceId + '/' + attributeScope + '?keys=' + keys;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
return attributeService.deleteEntityAttributes(types.entityType.device, deviceId, attributeScope, attributes);
}
function sendOneWayRpcCommand(deviceId, requestBody) {

136
ui/src/app/api/entity-relation.service.js

@ -0,0 +1,136 @@
/*
* 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.
*/
export default angular.module('thingsboard.api.entityRelation', [])
.factory('entityRelationService', EntityRelationService)
.name;
/*@ngInject*/
function EntityRelationService($http, $q) {
var service = {
saveRelation: saveRelation,
deleteRelation: deleteRelation,
deleteRelations: deleteRelations,
findByFrom: findByFrom,
findByFromAndType: findByFromAndType,
findByTo: findByTo,
findByToAndType: findByToAndType,
findByQuery: findByQuery
}
return service;
function saveRelation(relation) {
var deferred = $q.defer();
var url = '/api/relation';
$http.post(url, relation).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function deleteRelation(fromId, fromType, relationType, toId, toType) {
var deferred = $q.defer();
var url = '/api/relation?fromId=' + fromId;
url += '&fromType=' + fromType;
url += '&relationType=' + relationType;
url += '&toId=' + toId;
url += '&toType=' + toType;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function deleteRelations(entityId, entityType) {
var deferred = $q.defer();
var url = '/api/relations?entityId=' + entityId;
url += '&entityType=' + entityType;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByFrom(fromId, fromType) {
var deferred = $q.defer();
var url = '/api/relations?fromId=' + fromId;
url += '&fromType=' + fromType;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByFromAndType(fromId, fromType, relationType) {
var deferred = $q.defer();
var url = '/api/relations?fromId=' + fromId;
url += '&fromType=' + fromType;
url += '&relationType=' + relationType;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByTo(toId, toType) {
var deferred = $q.defer();
var url = '/api/relations?toId=' + toId;
url += '&toType=' + toType;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByToAndType(toId, toType, relationType) {
var deferred = $q.defer();
var url = '/api/relations?toId=' + toId;
url += '&toType=' + toType;
url += '&relationType=' + relationType;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByQuery(query) {
var deferred = $q.defer();
var url = '/api/relations';
$http.post(url, query).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

477
ui/src/app/api/entity.service.js

@ -0,0 +1,477 @@
/*
* 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.
*/
import thingsboardTypes from '../common/types.constant';
export default angular.module('thingsboard.api.entity', [thingsboardTypes])
.factory('entityService', EntityService)
.name;
/*@ngInject*/
function EntityService($http, $q, userService, deviceService,
assetService, tenantService, customerService,
ruleService, pluginService, types, utils) {
var service = {
getEntity: getEntity,
getEntities: getEntities,
getEntitiesByNameFilter: getEntitiesByNameFilter,
entityName: entityName,
processEntityAliases: processEntityAliases,
getEntityKeys: getEntityKeys,
checkEntityAlias: checkEntityAlias,
createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo
};
return service;
function getEntityPromise(entityType, entityId, config) {
var promise;
switch (entityType) {
case types.entityType.device:
promise = deviceService.getDevice(entityId, true, config);
break;
case types.entityType.asset:
promise = assetService.getAsset(entityId, true, config);
break;
case types.entityType.tenant:
promise = tenantService.getTenant(entityId);
break;
case types.entityType.customer:
promise = customerService.getCustomer(entityId);
break;
case types.entityType.rule:
promise = ruleService.getRule(entityId);
break;
case types.entityType.plugin:
promise = pluginService.getPlugin(entityId);
break;
}
return promise;
}
function getEntity(entityType, entityId, config) {
var deferred = $q.defer();
var promise = getEntityPromise(entityType, entityId, config);
promise.then(
function success(result) {
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getEntitiesByIdsPromise(fetchEntityFunction, entityIds) {
var tasks = [];
var deferred = $q.defer();
for (var i=0;i<entityIds.length;i++) {
tasks.push(fetchEntityFunction(entityIds[i]));
}
$q.all(tasks).then(
function success(entities) {
if (entities) {
entities.sort(function (entity1, entity2) {
var id1 = entity1.id.id;
var id2 = entity2.id.id;
var index1 = entityIds.indexOf(id1);
var index2 = entityIds.indexOf(id2);
return index1 - index2;
});
deferred.resolve(entities);
} else {
deferred.resolve([]);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getEntitiesPromise(entityType, entityIds, config) {
var promise;
switch (entityType) {
case types.entityType.device:
promise = deviceService.getDevices(entityIds, config);
break;
case types.entityType.asset:
promise = assetService.getAssets(entityIds, config);
break;
case types.entityType.tenant:
promise = getEntitiesByIdsPromise(tenantService.getTenant, entityIds);
break;
case types.entityType.customer:
promise = getEntitiesByIdsPromise(customerService.getCustomer, entityIds);
break;
case types.entityType.rule:
promise = getEntitiesByIdsPromise(ruleService.getRule, entityIds);
break;
case types.entityType.plugin:
promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds);
break;
}
return promise;
}
function getEntities(entityType, entityIds, config) {
var deferred = $q.defer();
var promise = getEntitiesPromise(entityType, entityIds, config);
promise.then(
function success(result) {
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getEntitiesByPageLinkPromise(entityType, pageLink, config) {
var promise;
var user = userService.getCurrentUser();
var customerId = user.customerId;
switch (entityType) {
case types.entityType.device:
if (user.authority === 'CUSTOMER_USER') {
promise = deviceService.getCustomerDevices(customerId, pageLink, false, config);
} else {
promise = deviceService.getTenantDevices(pageLink, false, config);
}
break;
case types.entityType.asset:
if (user.authority === 'CUSTOMER_USER') {
promise = assetService.getCustomerAssets(customerId, pageLink, false, config);
} else {
promise = assetService.getTenantAssets(pageLink, false, config);
}
break;
case types.entityType.tenant:
promise = tenantService.getTenants(pageLink);
break;
case types.entityType.customer:
promise = customerService.getCustomers(pageLink);
break;
case types.entityType.rule:
promise = ruleService.getAllRules(pageLink);
break;
case types.entityType.plugin:
promise = pluginService.getAllPlugins(pageLink);
break;
}
return promise;
}
function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config) {
var deferred = $q.defer();
var pageLink = {limit: limit, textSearch: entityNameFilter};
var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config);
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
} else {
deferred.resolve(null);
}
},
function fail() {
deferred.resolve(null);
}
);
return deferred.promise;
}
function entityName(entityType, entity) {
var name = '';
switch (entityType) {
case types.entityType.device:
case types.entityType.asset:
case types.entityType.rule:
case types.entityType.plugin:
name = entity.name;
break;
case types.entityType.tenant:
case types.entityType.customer:
name = entity.title;
break;
}
return name;
}
function entityToEntityInfo(entityType, entity) {
return { name: entityName(entityType, entity), entityType: entityType, id: entity.id.id };
}
function entitiesToEntitiesInfo(entityType, entities) {
var entitiesInfo = [];
for (var d = 0; d < entities.length; d++) {
entitiesInfo.push(entityToEntityInfo(entityType, entities[d]));
}
return entitiesInfo;
}
function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) {
if (index < aliasIds.length) {
var aliasId = aliasIds[index];
var entityAlias = entityAliases[aliasId];
var alias = entityAlias.alias;
var entityFilter = entityAlias.entityFilter;
if (entityFilter.useFilter) {
var entityNameFilter = entityFilter.entityNameFilter;
getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then(
function(entities) {
if (entities && entities != null) {
var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};
resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
}
});
} else {
var entityList = entityFilter.entityList;
getEntities(entityAlias.entityType, entityList).then(
function success(entities) {
if (entities && entities.length > 0) {
var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};
resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
}
},
function fail() {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
}
);
}
} else {
deferred.resolve(resolution);
}
}
function processEntityAliases(entityAliases) {
var deferred = $q.defer();
var resolution = {
aliasesInfo: {
entityAliases: {},
entityAliasesInfo: {}
}
};
var aliasIds = [];
if (entityAliases) {
for (var aliasId in entityAliases) {
aliasIds.push(aliasId);
}
}
processEntityAlias(0, aliasIds, entityAliases, resolution, deferred);
return deferred.promise;
}
function getEntityKeys(entityType, entityId, query, type) {
var deferred = $q.defer();
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/';
if (type === types.dataKeyType.timeseries) {
url += 'timeseries';
} else if (type === types.dataKeyType.attribute) {
url += 'attributes';
}
$http.get(url, null).then(function success(response) {
var result = [];
if (response.data) {
if (query) {
var dataKeys = response.data;
var lowercaseQuery = angular.lowercase(query);
for (var i=0; i<dataKeys.length;i++) {
if (angular.lowercase(dataKeys[i]).indexOf(lowercaseQuery) === 0) {
result.push(dataKeys[i]);
}
}
} else {
result = response.data;
}
}
deferred.resolve(result);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function checkEntityAlias(entityAlias) {
var deferred = $q.defer();
var entityType = entityAlias.entityType;
var entityFilter = entityAlias.entityFilter;
var promise;
if (entityFilter.useFilter) {
var entityNameFilter = entityFilter.entityNameFilter;
promise = getEntitiesByNameFilter(entityType, entityNameFilter, 1);
} else {
var entityList = entityFilter.entityList;
promise = getEntities(entityType, entityList);
}
promise.then(
function success(entities) {
if (entities && entities.length > 0) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
},
function fail() {
deferred.resolve(false);
}
);
return deferred.promise;
}
function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) {
var deferred = $q.defer();
var datasources = [];
processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
return deferred.promise;
}
function processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred) {
if (index < subscriptionsInfo.length) {
var subscriptionInfo = validateSubscriptionInfo(subscriptionsInfo[index]);
if (subscriptionInfo.type === types.datasourceType.entity) {
if (subscriptionInfo.entityId) {
getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, {ignoreLoading: true}).then(
function success(entity) {
createDatasourceFromSubscription(subscriptionInfo, datasources, entity);
index++;
processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
},
function fail() {
index++;
processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
}
);
} else if (subscriptionInfo.entityName || subscriptionInfo.entityNamePrefix
|| subscriptionInfo.entityIds) {
var promise;
if (subscriptionInfo.entityName) {
promise = getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName, 1, {ignoreLoading: true});
} else if (subscriptionInfo.entityNamePrefix) {
promise = getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix, 100, {ignoreLoading: true});
} else if (subscriptionInfo.entityIds) {
promise = getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds, {ignoreLoading: true});
}
promise.then(
function success(entities) {
if (entities && entities.length > 0) {
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
createDatasourceFromSubscription(subscriptionInfo, datasources, entity);
}
}
index++;
processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
},
function fail() {
index++;
processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
}
)
} else {
index++;
processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
}
} else if (subscriptionInfo.type === types.datasourceType.function) {
createDatasourceFromSubscription(subscriptionInfo, datasources);
index++;
processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
}
} else {
deferred.resolve(datasources);
}
}
function validateSubscriptionInfo(subscriptionInfo) {
if (subscriptionInfo.type === 'device') {
subscriptionInfo.type = types.datasourceType.entity;
subscriptionInfo.entityType = types.entityType.device;
if (subscriptionInfo.deviceId) {
subscriptionInfo.entityId = subscriptionInfo.deviceId;
} else if (subscriptionInfo.deviceName) {
subscriptionInfo.entityName = subscriptionInfo.deviceName;
} else if (subscriptionInfo.deviceNamePrefix) {
subscriptionInfo.entityNamePrefix = subscriptionInfo.deviceNamePrefix;
} else if (subscriptionInfo.deviceIds) {
subscriptionInfo.entityIds = subscriptionInfo.deviceIds;
}
}
return subscriptionInfo;
}
function createDatasourceFromSubscription(subscriptionInfo, datasources, entity) {
var datasource;
if (subscriptionInfo.type === types.datasourceType.entity) {
datasource = {
type: subscriptionInfo.type,
entityName: entity.name ? entity.name : entity.title,
name: entity.name ? entity.name : entity.title,
entityType: subscriptionInfo.entityType,
entityId: entity.id.id,
dataKeys: []
}
} else if (subscriptionInfo.type === types.datasourceType.function) {
datasource = {
type: subscriptionInfo.type,
name: subscriptionInfo.name || types.datasourceType.function,
dataKeys: []
}
}
datasources.push(datasource);
if (subscriptionInfo.timeseries) {
createDatasourceKeys(subscriptionInfo.timeseries, types.dataKeyType.timeseries, datasource, datasources);
}
if (subscriptionInfo.attributes) {
createDatasourceKeys(subscriptionInfo.attributes, types.dataKeyType.attribute, datasource, datasources);
}
if (subscriptionInfo.functions) {
createDatasourceKeys(subscriptionInfo.functions, types.dataKeyType.function, datasource, datasources);
}
}
function createDatasourceKeys(keyInfos, type, datasource, datasources) {
for (var i=0;i<keyInfos.length;i++) {
var keyInfo = keyInfos[i];
var dataKey = utils.createKey(keyInfo, type, datasources);
datasource.dataKeys.push(dataKey);
}
}
}

123
ui/src/app/api/subscription.js

@ -64,7 +64,7 @@ export default class Subscription {
this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || function(){};
this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){};
this.datasources = options.datasources;
this.datasources = this.ctx.utils.validateDatasources(options.datasources);
this.datasourceListeners = [];
this.data = [];
this.hiddenData = [];
@ -172,12 +172,6 @@ export default class Subscription {
this.startWatchingTimewindow();
}
}
registration = this.ctx.$scope.$on('deviceAliasListChanged', function () {
subscription.checkSubscriptions();
});
this.registrations.push(registration);
}
startWatchingTimewindow() {
@ -204,29 +198,11 @@ export default class Subscription {
}
initRpc() {
if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
this.targetDeviceAliasId = this.targetDeviceAliasIds[0];
if (this.ctx.aliasesInfo.deviceAliases[this.targetDeviceAliasId]) {
this.targetDeviceId = this.ctx.aliasesInfo.deviceAliases[this.targetDeviceAliasId].deviceId;
if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {
this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;
}
var subscription = this;
var registration = this.ctx.$scope.$on('deviceAliasListChanged', function () {
var deviceId = null;
if (subscription.ctx.aliasesInfo.deviceAliases[subscription.targetDeviceAliasId]) {
deviceId = subscription.ctx.aliasesInfo.deviceAliases[subscription.targetDeviceAliasId].deviceId;
}
if (!angular.equals(deviceId, subscription.targetDeviceId)) {
subscription.targetDeviceId = deviceId;
if (subscription.targetDeviceId) {
subscription.rpcEnabled = true;
} else {
subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
}
subscription.callbacks.rpcStateChanged(subscription);
}
});
this.registrations.push(registration);
} else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
this.targetDeviceId = this.targetDeviceIds[0];
}
@ -342,6 +318,14 @@ export default class Subscription {
this.onDataUpdated();
}
onAliasesChanged() {
if (this.type === this.ctx.types.widgetType.rpc.value) {
this.checkRpcTarget();
} else {
this.checkSubscriptions();
}
}
onDataUpdated(apply) {
if (this.cafs['dataUpdated']) {
this.cafs['dataUpdated']();
@ -493,25 +477,30 @@ export default class Subscription {
var datasource = this.datasources[i];
if (angular.isFunction(datasource))
continue;
var deviceId = null;
if (datasource.type === this.ctx.types.datasourceType.device) {
var entityId = null;
var entityType = null;
if (datasource.type === this.ctx.types.datasourceType.entity) {
var aliasName = null;
var deviceName = null;
if (datasource.deviceId) {
deviceId = datasource.deviceId;
datasource.name = datasource.deviceName;
aliasName = datasource.deviceName;
deviceName = datasource.deviceName;
} else if (datasource.deviceAliasId && this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId]) {
deviceId = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].deviceId;
datasource.name = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].alias;
aliasName = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].alias;
deviceName = '';
var devicesInfo = this.ctx.aliasesInfo.deviceAliasesInfo[datasource.deviceAliasId];
for (var d = 0; d < devicesInfo.length; d++) {
if (devicesInfo[d].id === deviceId) {
deviceName = devicesInfo[d].name;
break;
var entityName = null;
if (datasource.entityId) {
entityId = datasource.entityId;
entityType = datasource.entityType;
datasource.name = datasource.entityName;
aliasName = datasource.entityName;
entityName = datasource.entityName;
} else if (datasource.entityAliasId) {
if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) {
entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId;
entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType;
datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;
aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;
entityName = '';
var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId];
for (var d = 0; d < entitiesInfo.length; d++) {
if (entitiesInfo[d].id === entityId) {
entityName = entitiesInfo[d].name;
break;
}
}
}
}
@ -519,7 +508,7 @@ export default class Subscription {
datasource.name = datasource.name || this.ctx.types.datasourceType.function;
}
for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, deviceName, aliasName);
updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName);
}
var subscription = this;
@ -528,7 +517,8 @@ export default class Subscription {
subscriptionType: this.type,
subscriptionTimewindow: this.subscriptionTimewindow,
datasource: datasource,
deviceId: deviceId,
entityType: entityType,
entityId: entityId,
dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
},
@ -563,19 +553,38 @@ export default class Subscription {
}
}
checkRpcTarget() {
var deviceId = null;
if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {
deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;
}
if (!angular.equals(deviceId, this.targetDeviceId)) {
this.targetDeviceId = deviceId;
if (this.targetDeviceId) {
this.rpcEnabled = true;
} else {
this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
}
this.callbacks.rpcStateChanged(this);
}
}
checkSubscriptions() {
var subscriptionsChanged = false;
for (var i = 0; i < this.datasourceListeners.length; i++) {
var listener = this.datasourceListeners[i];
var deviceId = null;
var entityId = null;
var entityType = null;
var aliasName = null;
if (listener.datasource.type === this.ctx.types.datasourceType.device) {
if (listener.datasource.deviceAliasId &&
this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId]) {
deviceId = this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].deviceId;
aliasName = this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].alias;
if (listener.datasource.type === this.ctx.types.datasourceType.entity) {
if (listener.datasource.entityAliasId &&
this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) {
entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId;
entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType;
aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias;
}
if (!angular.equals(deviceId, listener.deviceId) ||
if (!angular.equals(entityId, listener.entityId) ||
!angular.equals(entityType, listener.entityType) ||
!angular.equals(aliasName, listener.datasource.name)) {
subscriptionsChanged = true;
break;
@ -606,7 +615,7 @@ export default class Subscription {
const varsRegex = /\$\{([^\}]*)\}/g;
function updateDataKeyLabel(dataKey, dsName, deviceName, aliasName) {
function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) {
var pattern = dataKey.pattern;
var label = dataKey.pattern;
var match = varsRegex.exec(pattern);
@ -615,8 +624,10 @@ function updateDataKeyLabel(dataKey, dsName, deviceName, aliasName) {
var variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(dsName);
} else if (variableName === 'entityName') {
label = label.split(variable).join(entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(deviceName);
label = label.split(variable).join(entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(aliasName);
}

4
ui/src/app/api/widget.service.js

@ -20,6 +20,8 @@ import tinycolor from 'tinycolor2';
import thingsboardLedLight from '../components/led-light.directive';
import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget';
import agrologWidgets from '../widget/lib/agrolog';
import TbFlot from '../widget/lib/flot-widget';
import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
@ -32,7 +34,7 @@ import cssjs from '../../vendor/css.js/css';
import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, agrologWidgets,
thingsboardTypes, thingsboardUtils])
.factory('widgetService', WidgetService)
.name;

10
ui/src/app/app.js

@ -53,6 +53,7 @@ import thingsboardDialogs from './components/datakey-config-dialog.controller';
import thingsboardMenu from './services/menu.service';
import thingsboardRaf from './common/raf.provider';
import thingsboardUtils from './common/utils.service';
import thingsboardDashboardUtils from './common/dashboard-utils.service';
import thingsboardTypes from './common/types.constant';
import thingsboardApiTime from './api/time.service';
import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
@ -62,6 +63,10 @@ import thingsboardHome from './layout';
import thingsboardApiLogin from './api/login.service';
import thingsboardApiDevice from './api/device.service';
import thingsboardApiUser from './api/user.service';
import thingsboardApiEntityRelation from './api/entity-relation.service';
import thingsboardApiAsset from './api/asset.service';
import thingsboardApiAttribute from './api/attribute.service';
import thingsboardApiEntity from './api/entity.service';
import 'typeface-roboto';
import 'font-awesome/css/font-awesome.min.css';
@ -105,6 +110,7 @@ angular.module('thingsboard', [
thingsboardMenu,
thingsboardRaf,
thingsboardUtils,
thingsboardDashboardUtils,
thingsboardTypes,
thingsboardApiTime,
thingsboardKeyboardShortcut,
@ -114,6 +120,10 @@ angular.module('thingsboard', [
thingsboardApiLogin,
thingsboardApiDevice,
thingsboardApiUser,
thingsboardApiEntityRelation,
thingsboardApiAsset,
thingsboardApiAttribute,
thingsboardApiEntity,
uiRouter])
.config(AppConfig)
.factory('globalInterceptor', GlobalInterceptor)

45
ui/src/app/asset/add-asset.tpl.html

@ -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.
-->
<md-dialog aria-label="{{ 'asset.add' | translate }}" tb-help="'assets'" help-container-id="help-container">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>asset.add</h2>
<span flex></span>
<div id="help-container"></div>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-asset asset="vm.item" is-edit="true" the-form="theForm"></tb-asset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
{{ 'action.add' | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>

123
ui/src/app/asset/add-assets-to-customer.controller.js

@ -0,0 +1,123 @@
/*
* 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.
*/
/*@ngInject*/
export default function AddAssetsToCustomerController(assetService, $mdDialog, $q, customerId, assets) {
var vm = this;
vm.assets = assets;
vm.searchText = '';
vm.assign = assign;
vm.cancel = cancel;
vm.hasData = hasData;
vm.noData = noData;
vm.searchAssetTextUpdated = searchAssetTextUpdated;
vm.toggleAssetSelection = toggleAssetSelection;
vm.theAssets = {
getItemAtIndex: function (index) {
if (index > vm.assets.data.length) {
vm.theAssets.fetchMoreItems_(index);
return null;
}
var item = vm.assets.data[index];
if (item) {
item.indexNumber = index + 1;
}
return item;
},
getLength: function () {
if (vm.assets.hasNext) {
return vm.assets.data.length + vm.assets.nextPageLink.limit;
} else {
return vm.assets.data.length;
}
},
fetchMoreItems_: function () {
if (vm.assets.hasNext && !vm.assets.pending) {
vm.assets.pending = true;
assetService.getTenantAssets(vm.assets.nextPageLink, false).then(
function success(assets) {
vm.assets.data = vm.assets.data.concat(assets.data);
vm.assets.nextPageLink = assets.nextPageLink;
vm.assets.hasNext = assets.hasNext;
if (vm.assets.hasNext) {
vm.assets.nextPageLink.limit = vm.assets.pageSize;
}
vm.assets.pending = false;
},
function fail() {
vm.assets.hasNext = false;
vm.assets.pending = false;
});
}
}
};
function cancel () {
$mdDialog.cancel();
}
function assign() {
var tasks = [];
for (var assetId in vm.assets.selections) {
tasks.push(assetService.assignAssetToCustomer(customerId, assetId));
}
$q.all(tasks).then(function () {
$mdDialog.hide();
});
}
function noData() {
return vm.assets.data.length == 0 && !vm.assets.hasNext;
}
function hasData() {
return vm.assets.data.length > 0;
}
function toggleAssetSelection($event, asset) {
$event.stopPropagation();
var selected = angular.isDefined(asset.selected) && asset.selected;
asset.selected = !selected;
if (asset.selected) {
vm.assets.selections[asset.id.id] = true;
vm.assets.selectedCount++;
} else {
delete vm.assets.selections[asset.id.id];
vm.assets.selectedCount--;
}
}
function searchAssetTextUpdated() {
vm.assets = {
pageSize: vm.assets.pageSize,
data: [],
nextPageLink: {
limit: vm.assets.pageSize,
textSearch: vm.searchText
},
selections: {},
selectedCount: 0,
hasNext: true,
pending: false
};
}
}

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

Loading…
Cancel
Save