Browse Source

Merge remote-tracking branch 'origin/asset-alarm-mgmt' into dao-refactoring-vs

pull/118/head
mp-loki 10 years ago
parent
commit
0661de2392
  1. 1
      .gitignore
  2. 3
      application/.gitignore
  3. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 28
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  5. 199
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  6. 17
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  7. 2
      application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java
  8. 2
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  9. 2
      application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
  10. 4
      application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
  11. 45
      application/src/main/java/org/thingsboard/server/config/MvcCorsProperties.java
  12. 34
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  13. 234
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  14. 7
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  15. 83
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  16. 25
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  17. 16
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  18. 16
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  19. 194
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  20. 24
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  21. 6
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java
  22. 15
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java
  23. 69
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
  24. 34
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.java
  25. 64
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java
  26. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java
  27. 96
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java
  28. 12
      application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java
  29. 42
      application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.java
  30. 18
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  31. 30
      application/src/main/resources/thingsboard.yml
  32. 4
      application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java
  33. 4
      application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java
  34. 4
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  35. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  36. 55
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  37. 45
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmId.java
  38. 34
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
  39. 25
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSeverity.java
  40. 33
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java
  41. 166
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  42. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java
  43. 16
      common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java
  44. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java
  45. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdDeserializer.java
  46. 56
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  47. 37
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdSerializer.java
  48. 21
      common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java
  49. 103
      common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
  50. 10
      dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractModelDao.java
  51. 38
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  52. 77
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java
  53. 46
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  54. 252
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  55. 87
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  56. 106
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java
  57. 50
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetSearchQuery.java
  58. 59
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  59. 32
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java
  60. 282
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  61. 18
      dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java
  62. 10
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
  63. 11
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
  64. 76
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  65. 4
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  66. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  67. 37
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  68. 236
      dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java
  69. 235
      dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java
  70. 46
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  71. 18
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/EventEntity.java
  72. 29
      dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmSeverityCodec.java
  73. 27
      dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmStatusCodec.java
  74. 14
      dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
  75. 3
      dao/src/main/java/org/thingsboard/server/dao/plugin/PluginService.java
  76. 279
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
  77. 261
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  78. 31
      dao/src/main/java/org/thingsboard/server/dao/relation/EntityRelationsQuery.java
  79. 25
      dao/src/main/java/org/thingsboard/server/dao/relation/EntitySearchDirection.java
  80. 35
      dao/src/main/java/org/thingsboard/server/dao/relation/EntityTypeFilter.java
  81. 47
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  82. 52
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  83. 48
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationsSearchParameters.java
  84. 14
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
  85. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleService.java
  86. 14
      dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
  87. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
  88. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java
  89. 4
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  90. 28
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  91. 45
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  92. 77
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  93. 13
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
  94. 11
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  95. 4
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  96. 82
      dao/src/main/resources/schema.cql
  97. 2
      dao/src/main/resources/system-data.cql
  98. 9
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  99. 98
      dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
  100. 78
      dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceImplTest.java

1
.gitignore

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

3
application/.gitignore

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

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;

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

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

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

@ -24,17 +24,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.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
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.TenantId;
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.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;
@ -89,103 +91,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<>();
@ -268,24 +274,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);
}
@ -295,8 +378,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(),

45
application/src/main/java/org/thingsboard/server/config/MvcCorsProperties.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.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import java.util.HashMap;
import java.util.Map;
/**
* Created by yyh on 2017/5/2.
* CORS configuration
*/
@Configuration
@ConfigurationProperties(prefix = "spring.mvc.cors")
public class MvcCorsProperties {
private Map<String, CorsConfiguration> mappings = new HashMap<>();
public MvcCorsProperties() {
}
public Map<String, CorsConfiguration> getMappings() {
return mappings;
}
public void setMappings(Map<String, CorsConfiguration> mappings) {
this.mappings = mappings;
}
}

34
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -18,7 +18,9 @@ package org.thingsboard.server.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@ -34,11 +36,15 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
import org.thingsboard.server.service.security.auth.jwt.*;
import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter;
import java.util.ArrayList;
import java.util.Arrays;
@ -56,6 +62,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
public static final String WEBJARS_ENTRY_POINT = "/webjars/**";
public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
public static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
@ -87,10 +94,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
return filter;
}
@Bean
protected RestPublicLoginProcessingFilter buildRestPublicLoginProcessingFilter() throws Exception {
RestPublicLoginProcessingFilter filter = new RestPublicLoginProcessingFilter(PUBLIC_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
@Bean
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List<String> pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT));
pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher);
@ -135,6 +150,8 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().disable().frameOptions().disable()
.and()
.cors()
.and()
.csrf().disable()
.exceptionHandling()
@ -146,6 +163,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
.antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars
.antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
.and()
@ -156,8 +174,22 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
.and()
.addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
@ConditionalOnMissingBean(CorsFilter.class)
public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {
if (mvcCorsProperties.getMappings().size() == 0) {
return new CorsFilter(new UrlBasedCorsConfigurationSource());
} else {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.setCorsConfigurations(mvcCorsProperties.getMappings());
return new CorsFilter(source);
}
}
}

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

7
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -36,6 +36,7 @@ import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@ -167,7 +168,8 @@ public class AuthController extends BaseController {
String encodedPassword = passwordEncoder.encode(password);
UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword);
User user = userService.findUserById(credentials.getUserId());
SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
String baseUrl = constructBaseUrl(request);
String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail();
@ -201,7 +203,8 @@ public class AuthController extends BaseController {
userCredentials.setResetToken(null);
userCredentials = userService.saveUserCredentials(userCredentials);
User user = userService.findUserById(userCredentials.getUserId());
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal);
String baseUrl = constructBaseUrl(request);
String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail();

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

@ -22,10 +22,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;
@ -36,6 +34,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;
@ -44,6 +43,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;
@ -78,6 +78,9 @@ public abstract class BaseController {
@Autowired
protected DeviceService deviceService;
@Autowired
protected AssetService assetService;
@Autowired
protected DeviceCredentialsService deviceCredentialsService;
@ -102,6 +105,9 @@ public abstract class BaseController {
@Autowired
protected ActorService actorService;
@Autowired
protected RelationService relationService;
@ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
@ -251,6 +257,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);
@ -270,6 +313,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);
@ -385,6 +447,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();
@ -410,7 +482,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",

25
application/src/main/java/org/thingsboard/server/controller/CustomerController.java

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@ -42,6 +45,28 @@ public class CustomerController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET)
@ResponseBody
public JsonNode getShortCustomerInfoById(@PathVariable("customerId") String strCustomerId) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId);
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode infoObject = objectMapper.createObjectNode();
infoObject.put("title", customer.getTitle());
boolean isPublic = false;
if (customer.getAdditionalInfo() != null && customer.getAdditionalInfo().has("isPublic")) {
isPublic = customer.getAdditionalInfo().get("isPublic").asBoolean();
}
infoObject.put("isPublic", isPublic);
return infoObject;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text")
@ResponseBody

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

@ -18,6 +18,7 @@ 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.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId;
@ -116,6 +117,21 @@ public class DashboardController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody
public Dashboard assignDashboardToPublicCustomer(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
checkParameter("dashboardId", strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody

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

@ -19,6 +19,7 @@ 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.Device;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
@ -114,6 +115,21 @@ public class DeviceController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public Device assignDeviceToPublicCustomer(@PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
checkParameter("deviceId", strDeviceId);
try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId);
Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId());
return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId()));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET)
@ResponseBody

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

69
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java

@ -16,32 +16,40 @@
package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
import java.util.UUID;
@Component
public class RefreshTokenAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenFactory tokenFactory;
private final UserService userService;
private final CustomerService customerService;
@Autowired
public RefreshTokenAuthenticationProvider(final UserService userService, final JwtTokenFactory tokenFactory) {
public RefreshTokenAuthenticationProvider(final UserService userService, final CustomerService customerService, final JwtTokenFactory tokenFactory) {
this.userService = userService;
this.customerService = customerService;
this.tokenFactory = tokenFactory;
}
@ -50,8 +58,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
Assert.notNull(authentication, "No authentication data provided");
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
UserPrincipal principal = unsafeUser.getUserPrincipal();
SecurityUser securityUser;
if (principal.getType() == UserPrincipal.Type.USER_NAME) {
securityUser = authenticateByUserId(unsafeUser.getId());
} else {
securityUser = authenticateByPublicId(principal.getValue());
}
return new RefreshAuthenticationToken(securityUser);
}
User user = userService.findUserById(unsafeUser.getId());
private SecurityUser authenticateByUserId(UserId userId) {
User user = userService.findUserById(userId);
if (user == null) {
throw new UsernameNotFoundException("User not found by refresh token");
}
@ -67,9 +85,44 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled());
UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
return new RefreshAuthenticationToken(securityUser);
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
return securityUser;
}
private SecurityUser authenticateByPublicId(String publicId) {
CustomerId customerId;
try {
customerId = new CustomerId(UUID.fromString(publicId));
} catch (Exception e) {
throw new BadCredentialsException("Refresh token is not valid");
}
Customer publicCustomer = customerService.findCustomerById(customerId);
if (publicCustomer == null) {
throw new UsernameNotFoundException("Public entity not found by refresh token");
}
boolean isPublic = false;
if (publicCustomer.getAdditionalInfo() != null && publicCustomer.getAdditionalInfo().has("isPublic")) {
isPublic = publicCustomer.getAdditionalInfo().get("isPublic").asBoolean();
}
if (!isPublic) {
throw new BadCredentialsException("Refresh token is not valid");
}
User user = new User(new UserId(UUIDBased.EMPTY));
user.setTenantId(publicCustomer.getTenantId());
user.setCustomerId(publicCustomer.getId());
user.setEmail(publicId);
user.setAuthority(Authority.CUSTOMER_USER);
user.setFirstName("Public");
user.setLastName("Public");
UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.PUBLIC_ID, publicId);
SecurityUser securityUser = new SecurityUser(user, true, userPrincipal);
return securityUser;
}
@Override

34
application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.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.service.security.auth.rest;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PublicLoginRequest {
private String publicId;
@JsonCreator
public PublicLoginRequest(@JsonProperty("publicId") String publicId) {
this.publicId = publicId;
}
public String getPublicId() {
return publicId;
}
}

64
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java

@ -23,20 +23,31 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import java.util.UUID;
@Component
public class RestAuthenticationProvider implements AuthenticationProvider {
private final BCryptPasswordEncoder encoder;
private final UserService userService;
private final CustomerService customerService;
@Autowired
public RestAuthenticationProvider(final UserService userService, final BCryptPasswordEncoder encoder) {
public RestAuthenticationProvider(final UserService userService, final CustomerService customerService, final BCryptPasswordEncoder encoder) {
this.userService = userService;
this.customerService = customerService;
this.encoder = encoder;
}
@ -44,9 +55,23 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.notNull(authentication, "No authentication data provided");
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
Object principal = authentication.getPrincipal();
if (!(principal instanceof UserPrincipal)) {
throw new BadCredentialsException("Authentication Failed. Bad user principal.");
}
UserPrincipal userPrincipal = (UserPrincipal) principal;
if (userPrincipal.getType() == UserPrincipal.Type.USER_NAME) {
String username = userPrincipal.getValue();
String password = (String) authentication.getCredentials();
return authenticateByUsernameAndPassword(userPrincipal, username, password);
} else {
String publicId = userPrincipal.getValue();
return authenticateByPublicId(userPrincipal, publicId);
}
}
private Authentication authenticateByUsernameAndPassword(UserPrincipal userPrincipal, String username, String password) {
User user = userService.findUserByEmail(username);
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
@ -67,7 +92,38 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled());
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
}
private Authentication authenticateByPublicId(UserPrincipal userPrincipal, String publicId) {
CustomerId customerId;
try {
customerId = new CustomerId(UUID.fromString(publicId));
} catch (Exception e) {
throw new BadCredentialsException("Authentication Failed. Public Id is not valid.");
}
Customer publicCustomer = customerService.findCustomerById(customerId);
if (publicCustomer == null) {
throw new UsernameNotFoundException("Public entity not found: " + publicId);
}
boolean isPublic = false;
if (publicCustomer.getAdditionalInfo() != null && publicCustomer.getAdditionalInfo().has("isPublic")) {
isPublic = publicCustomer.getAdditionalInfo().get("isPublic").asBoolean();
}
if (!isPublic) {
throw new BadCredentialsException("Authentication Failed. Public Id is not valid.");
}
User user = new User(new UserId(UUIDBased.EMPTY));
user.setTenantId(publicCustomer.getTenantId());
user.setCustomerId(publicCustomer.getId());
user.setEmail(publicId);
user.setAuthority(Authority.CUSTOMER_USER);
user.setFirstName("Public");
user.setLastName("Public");
SecurityUser securityUser = new SecurityUser(user, true, userPrincipal);
return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
}

5
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java

@ -29,6 +29,7 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.model.UserPrincipal;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@ -73,7 +74,9 @@ public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingF
throw new AuthenticationServiceException("Username or Password not provided");
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, loginRequest.getUsername());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, loginRequest.getPassword());
return this.getAuthenticationManager().authenticate(token);
}

96
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java

@ -0,0 +1,96 @@
/**
* 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.service.security.auth.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.model.UserPrincipal;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RestPublicLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
private static Logger logger = LoggerFactory.getLogger(RestPublicLoginProcessingFilter.class);
private final AuthenticationSuccessHandler successHandler;
private final AuthenticationFailureHandler failureHandler;
private final ObjectMapper objectMapper;
public RestPublicLoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler,
AuthenticationFailureHandler failureHandler, ObjectMapper mapper) {
super(defaultProcessUrl);
this.successHandler = successHandler;
this.failureHandler = failureHandler;
this.objectMapper = mapper;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
if (!HttpMethod.POST.name().equals(request.getMethod())) {
if(logger.isDebugEnabled()) {
logger.debug("Authentication method not supported. Request method: " + request.getMethod());
}
throw new AuthMethodNotSupportedException("Authentication method not supported");
}
PublicLoginRequest loginRequest;
try {
loginRequest = objectMapper.readValue(request.getReader(), PublicLoginRequest.class);
} catch (Exception e) {
throw new AuthenticationServiceException("Invalid public login request payload");
}
if (StringUtils.isBlank(loginRequest.getPublicId())) {
throw new AuthenticationServiceException("Public Id is not provided");
}
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.PUBLIC_ID, loginRequest.getPublicId());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "");
return this.getAuthenticationManager().authenticate(token);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
successHandler.onAuthenticationSuccess(request, response, authResult);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, failed);
}
}

12
application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java

@ -30,6 +30,7 @@ public class SecurityUser extends User {
private Collection<GrantedAuthority> authorities;
private boolean enabled;
private UserPrincipal userPrincipal;
public SecurityUser() {
super();
@ -39,9 +40,10 @@ public class SecurityUser extends User {
super(id);
}
public SecurityUser(User user, boolean enabled) {
public SecurityUser(User user, boolean enabled, UserPrincipal userPrincipal) {
super(user);
this.enabled = enabled;
this.userPrincipal = userPrincipal;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
@ -57,8 +59,16 @@ public class SecurityUser extends User {
return enabled;
}
public UserPrincipal getUserPrincipal() {
return userPrincipal;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setUserPrincipal(UserPrincipal userPrincipal) {
this.userPrincipal = userPrincipal;
}
}

42
application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.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.service.security.model;
public class UserPrincipal {
private final Type type;
private final String value;
public UserPrincipal(Type type, String value) {
this.type = type;
this.value = value;
}
public Type getType() {
return type;
}
public String getValue() {
return value;
}
public enum Type {
USER_NAME,
PUBLIC_ID
}
}

18
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import java.util.Arrays;
import java.util.List;
@ -43,6 +44,7 @@ public class JwtTokenFactory {
private static final String FIRST_NAME = "firstName";
private static final String LAST_NAME = "lastName";
private static final String ENABLED = "enabled";
private static final String IS_PUBLIC = "isPublic";
private static final String TENANT_ID = "tenantId";
private static final String CUSTOMER_ID = "customerId";
@ -63,12 +65,15 @@ public class JwtTokenFactory {
if (securityUser.getAuthority() == null)
throw new IllegalArgumentException("User doesn't have any privileges");
Claims claims = Jwts.claims().setSubject(securityUser.getEmail());
UserPrincipal principal = securityUser.getUserPrincipal();
String subject = principal.getValue();
Claims claims = Jwts.claims().setSubject(subject);
claims.put(SCOPES, securityUser.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList()));
claims.put(USER_ID, securityUser.getId().getId().toString());
claims.put(FIRST_NAME, securityUser.getFirstName());
claims.put(LAST_NAME, securityUser.getLastName());
claims.put(ENABLED, securityUser.isEnabled());
claims.put(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID);
if (securityUser.getTenantId() != null) {
claims.put(TENANT_ID, securityUser.getTenantId().getId().toString());
}
@ -104,6 +109,9 @@ public class JwtTokenFactory {
securityUser.setFirstName(claims.get(FIRST_NAME, String.class));
securityUser.setLastName(claims.get(LAST_NAME, String.class));
securityUser.setEnabled(claims.get(ENABLED, Boolean.class));
boolean isPublic = claims.get(IS_PUBLIC, Boolean.class);
UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject);
securityUser.setUserPrincipal(principal);
String tenantId = claims.get(TENANT_ID, String.class);
if (tenantId != null) {
securityUser.setTenantId(new TenantId(UUID.fromString(tenantId)));
@ -123,9 +131,11 @@ public class JwtTokenFactory {
DateTime currentTime = new DateTime();
Claims claims = Jwts.claims().setSubject(securityUser.getEmail());
UserPrincipal principal = securityUser.getUserPrincipal();
Claims claims = Jwts.claims().setSubject(principal.getValue());
claims.put(SCOPES, Arrays.asList(Authority.REFRESH_TOKEN.name()));
claims.put(USER_ID, securityUser.getId().getId().toString());
claims.put(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID);
String token = Jwts.builder()
.setClaims(claims)
@ -150,8 +160,10 @@ public class JwtTokenFactory {
if (!scopes.get(0).equals(Authority.REFRESH_TOKEN.name())) {
throw new IllegalArgumentException("Invalid Refresh Token scope");
}
boolean isPublic = claims.get(IS_PUBLIC, Boolean.class);
UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject);
SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class))));
securityUser.setEmail(subject);
securityUser.setUserPrincipal(principal);
return securityUser;
}

30
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}"
@ -189,11 +189,33 @@ cache:
updates:
# Enable/disable updates checking.
enabled: "${UPDATES_ENABLED:true}"
# spring CORS configuration
spring.mvc.cors:
mappings:
# Intercept path
"/api/auth/**":
#Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled.
allowed-origins: "*"
#Comma-separated list of methods to allow. '*' allows all methods.
allowed-methods: "POST,GET,OPTIONS"
#Comma-separated list of headers to allow in a request. '*' allows all headers.
allowed-headers: "*"
#How long, in seconds, the response from a pre-flight request can be cached by clients.
max-age: "1800"
#Set whether credentials are supported. When not set, credentials are not supported.
allow-credentials: "true"
"/api/v1/**":
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
max-age: "1800"
allow-credentials: "true"
# SQL DAO Configuration
sql:
enabled: "${SQL_ENABLED:false}"
datasource:
url: "${SQL_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
username: "${SQL_DATASOURCE_USERNAME:postgres}"
password: "${SQL_DATASOURCE_PASSWORD:postgres}"
password: "${SQL_DATASOURCE_PASSWORD:postgres}"

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

4
application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java

@ -288,7 +288,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
for (int i=0;i<143;i++) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title);
@ -299,7 +299,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
for (int i=0;i<175;i++) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title);

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

@ -149,7 +149,7 @@ public class TenantControllerTest extends AbstractControllerTest {
List<Tenant> tenantsTitle1 = new ArrayList<>();
for (int i=0;i<134;i++) {
Tenant tenant = new Tenant();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title);
@ -159,7 +159,7 @@ public class TenantControllerTest extends AbstractControllerTest {
List<Tenant> tenantsTitle2 = new ArrayList<>();
for (int i=0;i<127;i++) {
Tenant tenant = new Tenant();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title);

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
}

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

@ -0,0 +1,55 @@
/**
* 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.AllArgsConstructor;
import lombok.Builder;
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
@Builder
@AllArgsConstructor
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);
}
}

10
dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractModelDao.java

@ -187,4 +187,14 @@ public abstract class CassandraAbstractModelDao<E extends BaseEntity<D>, D> exte
List<E> entities = findListByStatement(QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()));
return DaoUtil.convertDataList(entities);
}
protected static <T> Function<BaseEntity<T>, T> toDataFunction() {
return new Function<BaseEntity<T>, T>() {
@Nullable
@Override
public T apply(@Nullable BaseEntity<T> entity) {
return entity != null ? entity.toData() : null;
}
};
}
}

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

@ -0,0 +1,38 @@
/**
* 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;
import java.util.UUID;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmDao extends Dao<Alarm> {
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture<Alarm> findAlarmByIdAsync(UUID key);
Alarm save(Alarm alarm);
}

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

@ -0,0 +1,77 @@
/**
* 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.datastax.driver.core.querybuilder.QueryBuilder;
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.alarm.Alarm;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.CassandraAbstractModelDao;
import org.thingsboard.server.dao.model.AlarmEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
public class AlarmDaoImpl extends CassandraAbstractModelDao<AlarmEntity, Alarm> implements AlarmDao {
@Override
protected Class<AlarmEntity> getColumnFamilyClass() {
return AlarmEntity.class;
}
@Override
protected String getColumnFamilyName() {
return ALARM_COLUMN_FAMILY_NAME;
}
@Override
public Alarm save(Alarm alarm) {
log.debug("Save asset [{}] ", alarm);
return super.save(alarm);
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
Select select = select().from(ALARM_COLUMN_FAMILY_NAME);
Select.Where query = select.where();
query.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId()));
query.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, originator.getId()));
query.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, originator.getEntityType()));
query.and(eq(ALARM_TYPE_PROPERTY, type));
query.limit(1);
query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY));
return findOneByStatementAsync(query);
}
@Override
public ListenableFuture<Alarm> findAlarmByIdAsync(UUID key) {
log.debug("Get alarm by id {}", key);
Select.Where query = select().from(ALARM_BY_ID_VIEW_NAME).where(eq(ModelConstants.ID_PROPERTY, key));
query.limit(1);
log.trace("Execute query {}", query);
return findOneByStatementAsync(query);
}
}

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

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
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.DeviceId;
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<Alarm> findAlarmById(AlarmId alarmId);
ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
}

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

@ -0,0 +1,252 @@
/**
* 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.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.Tenant;
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.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.entity.BaseEntityService;
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.RelationService;
import org.thingsboard.server.dao.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
@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());
}
if (alarm.getEndTs() == 0L) {
alarm.setEndTs(alarm.getStartTs());
}
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
if (existing == null || existing.getStatus().isCleared()) {
log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(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 alarmDao.save(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<Alarm, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable Alarm alarm) {
if (alarm == null) {
return false;
} else {
AlarmStatus oldStatus = alarm.getStatus();
AlarmStatus newStatus = update.getStatus();
alarmDao.save(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<Alarm, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable Alarm alarm) {
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(alarm);
updateRelations(alarm, oldStatus, newStatus);
return true;
}
}
});
}
@Override
public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTime) {
return getAndUpdate(alarmId, new Function<Alarm, Boolean>() {
@Nullable
@Override
public Boolean apply(@Nullable Alarm alarm) {
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(alarm);
updateRelations(alarm, oldStatus, newStatus);
return true;
}
}
});
}
@Override
public ListenableFuture<Alarm> findAlarmById(AlarmId alarmId) {
log.trace("Executing findAlarmById [{}]", alarmId);
validateId(alarmId, "Incorrect alarmId " + alarmId);
return alarmDao.findAlarmByIdAsync(alarmId.getId());
}
@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<Alarm, Boolean> function) {
validateId(alarmId, "Alarm id should be specified!");
ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(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 {
Tenant tenant = tenantDao.findById(alarm.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
}
}
};
}

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

@ -0,0 +1,87 @@
/**
* 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.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* The Interface AssetDao.
*
*/
public interface AssetDao extends Dao<Asset> {
/**
* Save or update asset object
*
* @param asset the asset object
* @return saved asset object
*/
Asset 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<Asset> 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<Asset>> 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<Asset> 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<Asset>> 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<Asset> findAssetsByTenantIdAndName(UUID tenantId, String name);
}

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

@ -0,0 +1,106 @@
/**
* 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.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.DaoUtil;
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 CassandraAbstractSearchTextDao<AssetEntity, Asset> implements AssetDao {
@Override
protected Class<AssetEntity> getColumnFamilyClass() {
return AssetEntity.class;
}
@Override
protected String getColumnFamilyName() {
return ASSET_COLUMN_FAMILY_NAME;
}
@Override
public Asset save(Asset asset) {
log.debug("Save asset [{}] ", asset);
return save(asset);
}
@Override
public List<Asset> 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 DaoUtil.convertDataList(assetEntities);
}
@Override
public ListenableFuture<List<Asset>> 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<Asset> 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 DaoUtil.convertDataList(assetEntities);
}
@Override
public ListenableFuture<List<Asset>> 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<Asset> 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));
AssetEntity assetEntity = (AssetEntity) findOneByStatement(query);
return Optional.ofNullable(DaoUtil.getData(assetEntity));
}
}

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

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

@ -0,0 +1,282 @@
/**
* 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.Customer;
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.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.nosql.CustomerEntity;
import org.thingsboard.server.dao.model.nosql.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);
return assetDao.findById(assetId.getId());
}
@Override
public ListenableFuture<Asset> findAssetByIdAsync(AssetId assetId) {
log.trace("Executing findAssetById [{}]", assetId);
validateId(assetId, "Incorrect assetId " + assetId);
return assetDao.findByIdAsync(assetId.getId());
}
@Override
public Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name) {
log.trace("Executing findAssetByTenantIdAndName [{}][{}]", tenantId, name);
validateId(tenantId, "Incorrect tenantId " + tenantId);
return assetDao.findAssetsByTenantIdAndName(tenantId.getId(), name);
}
@Override
public Asset saveAsset(Asset asset) {
log.trace("Executing saveAsset [{}]", asset);
assetValidator.validate(asset);
return 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<Asset> assets = assetDao.findAssetsByTenantId(tenantId.getId(), pageLink);
return new TextPageData<>(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);
return assetDao.findAssetsByTenantIdAndIdsAsync(tenantId.getId(), toUUIDs(assetIds));
}
@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<Asset> assets = assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
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);
return assetDao.findAssetsByTenantIdCustomerIdAndIdsAsync(tenantId.getId(), customerId.getId(), toUUIDs(assetIds));
}
@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 {
Tenant 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)) {
Customer 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, Asset> tenantAssetsRemover =
new PaginatedRemover<TenantId, Asset>() {
@Override
protected List<Asset> findEntities(TenantId id, TextPageLink pageLink) {
return assetDao.findAssetsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(Asset entity) {
deleteAsset(new AssetId(entity.getId().getId()));
}
};
class CustomerAssetsUnassigner extends PaginatedRemover<CustomerId, Asset> {
private TenantId tenantId;
CustomerAssetsUnassigner(TenantId tenantId) {
this.tenantId = tenantId;
}
@Override
protected List<Asset> findEntities(CustomerId id, TextPageLink pageLink) {
return assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
}
@Override
protected void removeEntity(Asset entity) {
unassignAssetFromCustomer(new AssetId(entity.getId().getId()));
}
}
}

18
dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java

@ -15,6 +15,13 @@
*/
package org.thingsboard.server.dao.customer;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TITLE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TENANT_ID_PROPERTY;
import java.util.Optional;
import com.datastax.driver.core.querybuilder.Select;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
@ -53,4 +60,15 @@ public class CassandraCustomerDao extends CassandraAbstractSearchTextDao<Custome
return DaoUtil.convertDataList(customerEntities);
}
@Override
public Optional<Customer> findCustomersByTenantIdAndTitle(UUID tenantId, String title) {
Select select = select().from(CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME);
Select.Where query = select.where();
query.and(eq(CUSTOMER_TENANT_ID_PROPERTY, tenantId));
query.and(eq(CUSTOMER_TITLE_PROPERTY, title));
CustomerEntity customerEntity = findOneByStatement(query);
Customer customer = DaoUtil.getData(customerEntity);
return Optional.ofNullable(customer);
}
}

10
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.customer;
import java.util.Optional;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
@ -43,5 +44,14 @@ public interface CustomerDao extends Dao<Customer> {
* @return the list of customer objects
*/
List<Customer> findCustomersByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find customers by tenantId and customer title.
*
* @param tenantId the tenantId
* @param title the customer title
* @return the optional customer object
*/
Optional<Customer> findCustomersByTenantIdAndTitle(UUID tenantId, String title);
}

11
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;
@ -24,13 +25,17 @@ import org.thingsboard.server.common.data.page.TextPageLink;
public interface CustomerService {
Customer findCustomerById(CustomerId customerId);
ListenableFuture<Customer> findCustomerByIdAsync(CustomerId customerId);
Customer saveCustomer(Customer customer);
void deleteCustomer(CustomerId customerId);
Customer findOrCreatePublicCustomer(TenantId tenantId);
TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink);
void deleteCustomersByTenantId(TenantId tenantId);
}

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

@ -15,30 +15,42 @@
*/
package org.thingsboard.server.dao.customer;
import static org.thingsboard.server.dao.service.Validator.validateId;
import java.io.IOException;
import java.util.List;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
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.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService;
import java.util.List;
@Service
@Slf4j
public class CustomerServiceImpl implements CustomerService {
public class CustomerServiceImpl extends BaseEntityService implements CustomerService {
private static final String PUBLIC_CUSTOMER_TITLE = "Public";
@Autowired
private CustomerDao customerDao;
@ -62,6 +74,13 @@ public class CustomerServiceImpl implements CustomerService {
return customerDao.findById(customerId.getId());
}
@Override
public ListenableFuture<Customer> findCustomerByIdAsync(CustomerId customerId) {
log.trace("Executing findCustomerByIdAsync [{}]", customerId);
validateId(customerId, "Incorrect customerId " + customerId);
return customerDao.findByIdAsync(customerId.getId());
}
@Override
public Customer saveCustomer(Customer customer) {
log.trace("Executing saveCustomer [{}]", customer);
@ -72,24 +91,45 @@ public class CustomerServiceImpl implements CustomerService {
@Override
public void deleteCustomer(CustomerId customerId) {
log.trace("Executing deleteCustomer [{}]", customerId);
Validator.validateId(customerId, "Incorrect tenantId " + customerId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
Customer customer = findCustomerById(customerId);
if (customer == null) {
throw new IncorrectParameterException("Unable to delete non-existent customer.");
}
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());
}
@Override
public Customer findOrCreatePublicCustomer(TenantId tenantId) {
log.trace("Executing findOrCreatePublicCustomer, tenantId [{}]", tenantId);
Validator.validateId(tenantId, "Incorrect customerId " + tenantId);
Optional<Customer> publicCustomerOpt = customerDao.findCustomersByTenantIdAndTitle(tenantId.getId(), PUBLIC_CUSTOMER_TITLE);
if (publicCustomerOpt.isPresent()) {
return publicCustomerOpt.get();
} else {
Customer publicCustomer = new Customer();
publicCustomer.setTenantId(tenantId);
publicCustomer.setTitle(PUBLIC_CUSTOMER_TITLE);
try {
publicCustomer.setAdditionalInfo(new ObjectMapper().readValue("{ \"isPublic\": true }", JsonNode.class));
} catch (IOException e) {
throw new IncorrectParameterException("Unable to create public customer.", e);
}
return customerDao.save(publicCustomer);
}
}
@Override
public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink) {
log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<Customer> customers = customerDao.findCustomersByTenantId(tenantId.getId(), pageLink);
return new TextPageData<Customer>(customers, pageLink);
return new TextPageData<>(customers, pageLink);
}
@Override
@ -101,11 +141,35 @@ public class CustomerServiceImpl implements CustomerService {
private DataValidator<Customer> customerValidator =
new DataValidator<Customer>() {
@Override
protected void validateCreate(Customer customer) {
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> {
throw new DataValidationException("Customer with such title already exists!");
}
);
}
@Override
protected void validateUpdate(Customer customer) {
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> {
if (!c.getId().equals(customer.getUuidId())) {
throw new DataValidationException("Customer with such title already exists!");
}
}
);
}
@Override
protected void validateDataImpl(Customer customer) {
if (StringUtils.isEmpty(customer.getTitle())) {
throw new DataValidationException("Customer title should be specified!");
}
if (customer.getTitle().equals(PUBLIC_CUSTOMER_TITLE)) {
throw new DataValidationException("'Public' title for customer is system reserved!");
}
if (!StringUtils.isEmpty(customer.getEmail())) {
validateEmail(customer.getEmail());
}

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

@ -29,6 +29,7 @@ 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.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
@ -40,7 +41,7 @@ import java.util.List;
@Service
@Slf4j
public class DashboardServiceImpl implements DashboardService {
public class DashboardServiceImpl extends BaseEntityService implements DashboardService {
@Autowired
private DashboardDao dashboardDao;
@ -86,6 +87,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.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@ -48,7 +49,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;
@ -125,6 +126,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);
}
}

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

@ -0,0 +1,236 @@
/**
* 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;
}
public UUID getOriginatorId() {
return originatorId;
}
public void setOriginatorId(UUID originatorId) {
this.originatorId = originatorId;
}
public EntityType getOriginatorType() {
return originatorType;
}
public void setOriginatorType(EntityType originatorType) {
this.originatorType = originatorType;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public AlarmSeverity getSeverity() {
return severity;
}
public void setSeverity(AlarmSeverity severity) {
this.severity = severity;
}
public AlarmStatus getStatus() {
return status;
}
public void setStatus(AlarmStatus status) {
this.status = status;
}
public Long getStartTs() {
return startTs;
}
public void setStartTs(Long startTs) {
this.startTs = startTs;
}
public Long getEndTs() {
return endTs;
}
public void setEndTs(Long endTs) {
this.endTs = endTs;
}
public Long getAckTs() {
return ackTs;
}
public void setAckTs(Long ackTs) {
this.ackTs = ackTs;
}
public Long getClearTs() {
return clearTs;
}
public void setClearTs(Long clearTs) {
this.clearTs = clearTs;
}
public JsonNode getDetails() {
return details;
}
public void setDetails(JsonNode details) {
this.details = details;
}
public Boolean getPropagate() {
return propagate;
}
public void setPropagate(Boolean propagate) {
this.propagate = propagate;
}
@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;
}
}

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

@ -112,6 +112,7 @@ public class ModelConstants {
public static final String CUSTOMER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "customer_by_tenant_and_search_text";
public static final String CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME = "customer_by_tenant_and_title";
/**
* Cassandra device constants.
@ -126,6 +127,51 @@ 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 = TENANT_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 = TENANT_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";
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
/**
* 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.

18
dao/src/main/java/org/thingsboard/server/dao/model/nosql/EventEntity.java

@ -99,23 +99,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);

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

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

@ -15,11 +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.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,6 +32,7 @@ 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;
@ -43,9 +47,10 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
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);
@ -102,6 +107,12 @@ public class BasePluginService implements PluginService {
return pluginDao.findById(pluginId);
}
@Override
public ListenableFuture<PluginMetaData> findPluginByIdAsync(PluginId pluginId) {
validateId(pluginId, "Incorrect plugin id for search plugin request.");
return pluginDao.findByIdAsync(pluginId.getId());
}
@Override
public PluginMetaData findPluginByApiToken(String apiToken) {
Validator.validateString(apiToken, "Incorrect plugin apiToken for search request.");
@ -196,6 +207,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.CassandraAbstractAsyncDao;
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 CassandraAbstractAsyncDao 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);
}
}

14
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.plugin.PluginService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@ -50,7 +55,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);
@ -163,6 +168,12 @@ public class BaseRuleService implements RuleService {
return ruleDao.findById(ruleId.getId());
}
@Override
public ListenableFuture<RuleMetaData> findRuleByIdAsync(RuleId ruleId) {
validateId(ruleId, "Incorrect rule id for search rule request.");
return ruleDao.findByIdAsync(ruleId.getId());
}
@Override
public List<RuleMetaData> findPluginRules(String pluginToken) {
return ruleDao.findRulesByPlugin(pluginToken);
@ -228,6 +239,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

7
dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java

@ -28,6 +28,7 @@ import org.thingsboard.server.dao.model.sql.CustomerEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
@ -59,4 +60,10 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao<CustomerEntity, Cus
pageLink.getTextSearch(), pageLink.getIdOffset()));
}
}
@Override
public Optional<Customer> findCustomersByTenantIdAndTitle(UUID tenantId, String title) {
// TODO: vsosliuk implement
return null;
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java

@ -35,5 +35,4 @@ public interface EventRepository extends CrudRepository<EventEntity, UUID>, JpaS
EventEntity findByTenantIdAndEntityTypeAndEntityIdAndEventTypeAndEventUid(
UUID tenantId, EntityType entityType, UUID id, String eventType, String eventUid);
}

4
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;
@ -23,6 +24,8 @@ import org.thingsboard.server.common.data.page.TextPageLink;
public interface TenantService {
Tenant findTenantById(TenantId tenantId);
ListenableFuture<Tenant> findTenantByIdAsync(TenantId customerId);
Tenant saveTenant(Tenant tenant);
@ -31,5 +34,4 @@ public interface TenantService {
TextPageData<Tenant> findTenants(TextPageLink pageLink);
void deleteTenants();
}

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.tenant;
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;
@ -26,6 +27,7 @@ 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.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
@ -37,21 +39,23 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.util.List;
import static org.thingsboard.server.dao.service.Validator.validateId;
@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;
@ -66,7 +70,7 @@ public class TenantServiceImpl implements TenantService {
@Autowired
private PluginService pluginService;
@Override
public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId);
@ -74,6 +78,13 @@ public class TenantServiceImpl implements TenantService {
return tenantDao.findById(tenantId.getId());
}
@Override
public ListenableFuture<Tenant> findTenantByIdAsync(TenantId tenantId) {
log.trace("Executing TenantIdAsync [{}]", tenantId);
validateId(tenantId, "Incorrect tenantId " + tenantId);
return tenantDao.findByIdAsync(tenantId.getId());
}
@Override
public Tenant saveTenant(Tenant tenant) {
log.trace("Executing saveTenant [{}]", tenant);
@ -94,6 +105,7 @@ public class TenantServiceImpl implements TenantService {
ruleService.deleteRulesByTenantId(tenantId);
pluginService.deletePluginsByTenantId(tenantId);
tenantDao.removeById(tenantId.getId());
deleteEntityRelations(tenantId);
}
@Override
@ -122,10 +134,10 @@ public class TenantServiceImpl implements TenantService {
}
}
};
private PaginatedRemover<String, Tenant> tenantsRemover =
new PaginatedRemover<String, Tenant>() {
@Override
protected List<Tenant> findEntities(String region, TextPageLink pageLink) {
return tenantDao.findTenantsByRegion(region, pageLink);

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

@ -24,7 +24,7 @@ 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.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -49,30 +49,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");
}
@ -80,13 +80,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) {
@ -94,7 +94,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);
}
@ -109,15 +109,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) {

77
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.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.CassandraAbstractAsyncDao;
@ -93,8 +94,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
@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
@ -107,9 +108,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
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();
@ -118,7 +119,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
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);
@ -132,11 +133,11 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
}
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);
@ -144,13 +145,13 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
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);
@ -186,19 +187,19 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
}
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);
}
@ -208,21 +209,21 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
.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);
@ -234,30 +235,30 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
@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());
@ -266,11 +267,11 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
@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);
@ -278,11 +279,11 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
@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));
}
@ -338,9 +339,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
* 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);

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;
@ -31,17 +32,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

@ -19,6 +19,7 @@ import com.datastax.driver.core.ResultSet;
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.id.UUIDBased;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
@ -31,15 +32,15 @@ import java.util.List;
*/
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);

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

@ -31,6 +31,7 @@ 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.ModelConstants;
@ -44,7 +45,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
public class UserServiceImpl extends BaseEntityService implements UserService {
private static final int DEFAULT_TOKEN_LENGTH = 30;
@ -159,6 +160,7 @@ public class UserServiceImpl implements UserService {
validateId(userId, "Incorrect userId " + userId);
UserCredentials userCredentials = userCredentialsDao.findByUserId(userId.getId());
userCredentialsDao.removeById(userCredentials.getUuidId());
deleteEntityRelations(userId);
userDao.removeById(userId.getId());
}

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

@ -137,6 +137,13 @@ CREATE TABLE IF NOT EXISTS thingsboard.customer (
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_title AS
SELECT *
from thingsboard.customer
WHERE tenant_id IS NOT NULL AND title IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, title, id )
WITH CLUSTERING ORDER BY ( title ASC, id DESC );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search_text AS
SELECT *
from thingsboard.customer
@ -195,6 +202,81 @@ 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)
) WITH CLUSTERING ORDER BY ( type ASC, id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.alarm_by_id AS
SELECT *
from thingsboard.alarm
WHERE tenant_id IS NOT NULL AND originator_id IS NOT NULL AND originator_type IS NOT NULL AND type IS NOT NULL
AND type IS NOT NULL AND id IS NOT NULL
PRIMARY KEY (id, tenant_id, originator_id, originator_type, type)
WITH CLUSTERING ORDER BY ( tenant_id ASC, originator_id ASC, originator_type ASC, type ASC);
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,

2
dao/src/main/resources/system-data.cql

File diff suppressed because one or more lines are too long

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

@ -32,6 +32,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
@ -40,6 +41,7 @@ import org.thingsboard.server.common.data.plugin.ComponentScope;
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.alarm.AlarmService;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -47,6 +49,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 +113,12 @@ public abstract class AbstractServiceTest {
@Autowired
protected EventService eventService;
@Autowired
protected RelationService relationService;
@Autowired
protected AlarmService alarmService;
@Autowired
private ComponentDescriptorService componentDescriptorService;

98
dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java

@ -0,0 +1,98 @@
/**
* 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 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.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
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 AlarmServiceTest extends AbstractServiceTest {
public static final String TEST_ALARM = "TEST_ALARM";
private TenantId tenantId;
@Before
public void before() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
}
@Test
public void testSaveAndFetchAlarm() 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(relationService.saveRelation(relation).get());
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
.type(TEST_ALARM)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
Alarm created = alarmService.createOrUpdateAlarm(alarm);
Assert.assertNotNull(created);
Assert.assertNotNull(created.getId());
Assert.assertNotNull(created.getOriginator());
Assert.assertNotNull(created.getSeverity());
Assert.assertNotNull(created.getStatus());
Assert.assertEquals(tenantId, created.getTenantId());
Assert.assertEquals(childId, created.getOriginator());
Assert.assertEquals(TEST_ALARM, created.getType());
Assert.assertEquals(AlarmSeverity.CRITICAL, created.getSeverity());
Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, created.getStatus());
Assert.assertEquals(ts, created.getStartTs());
Assert.assertEquals(ts, created.getEndTs());
Assert.assertEquals(0L, created.getAckTs());
Assert.assertEquals(0L, created.getClearTs());
Alarm fetched = alarmService.findAlarmById(created.getId()).get();
Assert.assertEquals(created, fetched);
}
}

78
dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceImplTest.java

@ -33,11 +33,11 @@ import java.util.Collections;
import java.util.List;
public class CustomerServiceImplTest extends AbstractServiceTest {
private IdComparator<Customer> idComparator = new IdComparator<>();
private TenantId tenantId;
@Before
public void before() {
Tenant tenant = new Tenant();
@ -58,23 +58,23 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
customer.setTenantId(tenantId);
customer.setTitle("My customer");
Customer savedCustomer = customerService.saveCustomer(customer);
Assert.assertNotNull(savedCustomer);
Assert.assertNotNull(savedCustomer.getId());
Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
Assert.assertEquals(customer.getTenantId(), savedCustomer.getTenantId());
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
savedCustomer.setTitle("My new customer");
customerService.saveCustomer(savedCustomer);
Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
customerService.deleteCustomer(savedCustomer.getId());
}
@Test
public void testFindCustomerById() {
Customer customer = new Customer();
@ -86,21 +86,21 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(savedCustomer, foundCustomer);
customerService.deleteCustomer(savedCustomer.getId());
}
@Test(expected = DataValidationException.class)
public void testSaveCustomerWithEmptyTitle() {
Customer customer = new Customer();
customer.setTenantId(tenantId);
customerService.saveCustomer(customer);
}
@Test(expected = DataValidationException.class)
public void testSaveCustomerWithEmptyTenant() {
Customer customer = new Customer();
customer.setTitle("My customer");
customerService.saveCustomer(customer);
}
@Test(expected = DataValidationException.class)
public void testSaveCustomerWithInvalidTenant() {
Customer customer = new Customer();
@ -108,7 +108,7 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
customer.setTenantId(new TenantId(UUIDs.timeBased()));
customerService.saveCustomer(customer);
}
@Test(expected = DataValidationException.class)
public void testSaveCustomerWithInvalidEmail() {
Customer customer = new Customer();
@ -117,7 +117,7 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
customer.setEmail("invalid@mail");
customerService.saveCustomer(customer);
}
@Test
public void testDeleteCustomer() {
Customer customer = new Customer();
@ -128,23 +128,23 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
Assert.assertNull(foundCustomer);
}
@Test
public void testFindCustomersByTenantId() {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant);
TenantId tenantId = tenant.getId();
List<Customer> customers = new ArrayList<>();
for (int i=0;i<135;i++) {
for (int i = 0; i < 135; i++) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
customer.setTitle("Customer"+i);
customer.setTitle("Customer" + i);
customers.add(customerService.saveCustomer(customer));
}
List<Customer> loadedCustomers = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23);
TextPageData<Customer> pageData = null;
@ -155,47 +155,47 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(customers, idComparator);
Collections.sort(loadedCustomers, idComparator);
Assert.assertEquals(customers, loadedCustomers);
customerService.deleteCustomersByTenantId(tenantId);
pageLink = new TextPageLink(33);
pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId);
}
@Test
public void testFindCustomersByTenantIdAndTitle() {
String title1 = "Customer title 1";
List<Customer> customersTitle1 = new ArrayList<>();
for (int i=0;i<143;i++) {
for (int i = 0; i < 143; i++) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title);
customersTitle1.add(customerService.saveCustomer(customer));
}
String title2 = "Customer title 2";
List<Customer> customersTitle2 = new ArrayList<>();
for (int i=0;i<175;i++) {
for (int i = 0; i < 175; i++) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title);
customersTitle2.add(customerService.saveCustomer(customer));
}
List<Customer> loadedCustomersTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(15, title1);
TextPageData<Customer> pageData = null;
@ -206,12 +206,12 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(customersTitle1, idComparator);
Collections.sort(loadedCustomersTitle1, idComparator);
Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
List<Customer> loadedCustomersTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2);
do {
@ -224,22 +224,22 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
Collections.sort(customersTitle2, idComparator);
Collections.sort(loadedCustomersTitle2, idComparator);
Assert.assertEquals(customersTitle2, loadedCustomersTitle2);
for (Customer customer : loadedCustomersTitle1) {
customerService.deleteCustomer(customer.getId());
}
pageLink = new TextPageLink(4, title1);
pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (Customer customer : loadedCustomersTitle2) {
customerService.deleteCustomer(customer.getId());
}
pageLink = new TextPageLink(4, title2);
pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());

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

Loading…
Cancel
Save