Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into feature/device-profile-telemetry-proto-schema

pull/3740/head
ShvaykaD 6 years ago
parent
commit
f0f30dc9c7
  1. 1
      application/src/main/data/json/system/oauth2_config_templates/github_config.json
  2. 3
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  3. 6
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 11
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  6. 18
      application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
  7. 4
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  8. 5
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  10. 59
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java
  11. 12
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java
  12. 6
      application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java
  13. 4
      application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java
  14. 59
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  15. 13
      application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  17. 22
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  18. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  19. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  20. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java
  21. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java
  22. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  23. 63
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  24. 6
      application/src/main/resources/thingsboard.yml
  25. 6
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java
  26. 6
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java
  27. 4
      common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java
  28. 26
      common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java
  29. 47
      common/queue/src/main/proto/queue.proto
  30. 8
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  31. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java
  32. 12
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  33. 38
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java
  34. 47
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java
  35. 133
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java
  36. 35
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java
  37. 38
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java
  38. 26
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java
  39. 24
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java
  40. 34
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java
  41. 47
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java
  42. 30
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java
  43. 9
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java
  44. 192
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  45. 155
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java
  46. 24
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java
  47. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java
  48. 12
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java
  49. 93
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java
  50. 51
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
  51. 392
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  52. 7
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceProfileCache.java
  53. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  54. 22
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java
  55. 30
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  56. 38
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  57. 64
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
  58. 4
      transport/coap/src/main/resources/tb-coap-transport.yml
  59. 4
      transport/http/src/main/resources/tb-http-transport.yml
  60. 4
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  61. 4
      ui-ngx/src/app/core/http/oauth2.service.ts
  62. 5
      ui-ngx/src/app/core/services/menu.service.ts
  63. 17
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html
  64. 10
      ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.html
  65. 10
      ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html
  66. 10
      ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html
  67. 15
      ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html
  68. 1
      ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html
  69. 10
      ui-ngx/src/app/modules/home/components/dashboard/select-target-state-dialog.component.html
  70. 10
      ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html
  71. 1
      ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html
  72. 12
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html
  73. 10
      ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html
  74. 2
      ui-ngx/src/app/modules/home/components/filter/filter-text.component.html
  75. 5
      ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss
  76. 3
      ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts
  77. 12
      ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html
  78. 10
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html
  79. 12
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html
  80. 10
      ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html
  81. 10
      ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html
  82. 45
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html
  83. 4
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss
  84. 12
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html
  85. 25
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html
  86. 7
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss
  87. 45
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html
  88. 30
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss
  89. 3
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts
  90. 12
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html
  91. 25
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html
  92. 10
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss
  93. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts
  94. 8
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html
  95. 21
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts
  96. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  97. 4
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts
  98. 9
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts
  99. 10
      ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html
  100. 4
      ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts

1
application/src/main/data/json/system/oauth2_config_templates/github_config.json

@ -10,6 +10,7 @@
"mapperConfig": { "mapperConfig": {
"type": "GITHUB", "type": "GITHUB",
"basic": { "basic": {
"firstNameAttributeKey": "name",
"tenantNameStrategy": "DOMAIN" "tenantNameStrategy": "DOMAIN"
} }
}, },

3
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -94,7 +94,8 @@
"name": "Device Profile Node", "name": "Device Profile Node",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"persistAlarmRulesState": false "persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
} }
} }
], ],

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

@ -544,7 +544,11 @@ public class ActorSystemContext {
public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) { public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) {
log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS); if (delayInMs > 0) {
getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS);
} else {
ctx.tell(msg);
}
} }
} }

11
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -35,6 +35,7 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -477,6 +478,16 @@ class DefaultTbContext implements TbContext {
mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId()); mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId());
} }
@Override
public void addProfileListener(Consumer<DeviceProfile> listener) {
mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), listener);
}
@Override
public void removeProfileListener() {
mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId());
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("ruleNodeId", ruleNodeId.toString()); metaData.putValue("ruleNodeId", ruleNodeId.toString());

2
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -106,7 +106,6 @@ public class DeviceProfileController extends BaseController {
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
deviceProfileCache.put(savedDeviceProfile);
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(), tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
@ -132,7 +131,6 @@ public class DeviceProfileController extends BaseController {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE); DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE);
deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId); deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId);
deviceProfileCache.evict(deviceProfileId);
tbClusterService.onDeviceProfileDelete(deviceProfile, null); tbClusterService.onDeviceProfileDelete(deviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED);

18
application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
@ -36,6 +38,10 @@ import java.util.List;
@RequestMapping("/api") @RequestMapping("/api")
@Slf4j @Slf4j
public class OAuth2Controller extends BaseController { public class OAuth2Controller extends BaseController {
@Autowired
private OAuth2Configuration oAuth2Configuration;
@RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
@ -70,4 +76,16 @@ public class OAuth2Controller extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET)
@ResponseBody
public String getLoginProcessingUrl() throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\"";
} catch (Exception e) {
throw handleException(e);
}
}
} }

4
application/src/main/java/org/thingsboard/server/controller/TenantController.java

@ -92,6 +92,7 @@ public class TenantController extends BaseController {
installScripts.createDefaultRuleChains(tenant.getId()); installScripts.createDefaultRuleChains(tenant.getId());
} }
tenantProfileCache.evict(tenant.getId()); tenantProfileCache.evict(tenant.getId());
tbClusterService.onTenantChange(tenant, null);
return tenant; return tenant;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -105,9 +106,10 @@ public class TenantController extends BaseController {
checkParameter("tenantId", strTenantId); checkParameter("tenantId", strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = new TenantId(toUUID(strTenantId));
checkTenantId(tenantId, Operation.DELETE); Tenant tenant = checkTenantId(tenantId, Operation.DELETE);
tenantService.deleteTenant(tenantId); tenantService.deleteTenant(tenantId);
tenantProfileCache.evict(tenantId); tenantProfileCache.evict(tenantId);
tbClusterService.onTenantDelete(tenant, null);
tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);

5
application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
@ -96,6 +97,7 @@ public class TenantProfileController extends BaseController {
tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile));
tenantProfileCache.put(tenantProfile); tenantProfileCache.put(tenantProfile);
tbClusterService.onTenantProfileChange(tenantProfile, null);
tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(),
newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
return tenantProfile; return tenantProfile;
@ -111,8 +113,9 @@ public class TenantProfileController extends BaseController {
checkParameter("tenantProfileId", strTenantProfileId); checkParameter("tenantProfileId", strTenantProfileId);
try { try {
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
checkTenantProfileId(tenantProfileId, Operation.DELETE); TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE);
tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId); tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId);
tbClusterService.onTenantProfileDelete(profile, null);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

2
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -170,7 +170,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
ruleChainService.saveRuleChainMetaData(tenant.getId(), md); ruleChainService.saveRuleChainMetaData(tenant.getId(), md);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to update Tenant", e); log.error("[{}] Unable to update Tenant: {}", tenant.getId(), tenant.getName(), e);
} }
} }
}; };

59
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java

@ -21,14 +21,17 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@Service @Service
@Slf4j @Slf4j
@ -40,6 +43,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfilesMap = new ConcurrentHashMap<>(); private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfilesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, DeviceProfileId> devicesMap = new ConcurrentHashMap<>(); private final ConcurrentMap<DeviceId, DeviceProfileId> devicesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, ConcurrentMap<EntityId, Consumer<DeviceProfile>>> listeners = new ConcurrentHashMap<>();
public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) { public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) {
this.deviceProfileService = deviceProfileService; this.deviceProfileService = deviceProfileService;
@ -50,19 +54,21 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) { public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) {
DeviceProfile profile = deviceProfilesMap.get(deviceProfileId); DeviceProfile profile = deviceProfilesMap.get(deviceProfileId);
if (profile == null) { if (profile == null) {
profile = deviceProfilesMap.get(deviceProfileId); deviceProfileFetchLock.lock();
if (profile == null) { try {
deviceProfileFetchLock.lock(); profile = deviceProfilesMap.get(deviceProfileId);
try { if (profile == null) {
profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
if (profile != null) { if (profile != null) {
deviceProfilesMap.put(deviceProfileId, profile); deviceProfilesMap.put(deviceProfileId, profile);
log.debug("[{}] Fetch device profile into cache: {}", profile.getId(), profile);
} }
} finally {
deviceProfileFetchLock.unlock();
} }
} finally {
deviceProfileFetchLock.unlock();
} }
} }
log.trace("[{}] Found device profile in cache: {}", deviceProfileId, profile);
return profile; return profile;
} }
@ -85,12 +91,19 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
public void put(DeviceProfile profile) { public void put(DeviceProfile profile) {
if (profile.getId() != null) { if (profile.getId() != null) {
deviceProfilesMap.put(profile.getId(), profile); deviceProfilesMap.put(profile.getId(), profile);
log.debug("[{}] pushed device profile to cache: {}", profile.getId(), profile);
notifyListeners(profile);
} }
} }
@Override @Override
public void evict(DeviceProfileId profileId) { public void evict(TenantId tenantId, DeviceProfileId profileId) {
deviceProfilesMap.remove(profileId); DeviceProfile oldProfile = deviceProfilesMap.remove(profileId);
log.debug("[{}] evict device profile from cache: {}", profileId, oldProfile);
DeviceProfile newProfile = get(tenantId, profileId);
if (newProfile != null) {
notifyListeners(newProfile);
}
} }
@Override @Override
@ -98,4 +111,34 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
devicesMap.remove(deviceId); devicesMap.remove(deviceId);
} }
@Override
public void addListener(TenantId tenantId, EntityId listenerId, Consumer<DeviceProfile> listener) {
listeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, listener);
}
@Override
public DeviceProfile find(DeviceProfileId deviceProfileId) {
return deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, deviceProfileId);
}
@Override
public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName) {
return deviceProfileService.findOrCreateDeviceProfile(tenantId, profileName);
}
@Override
public void removeListener(TenantId tenantId, EntityId listenerId) {
ConcurrentMap<EntityId, Consumer<DeviceProfile>> tenantListeners = listeners.get(tenantId);
if (tenantListeners != null) {
tenantListeners.remove(listenerId);
}
}
private void notifyListeners(DeviceProfile profile) {
ConcurrentMap<EntityId, Consumer<DeviceProfile>> tenantListeners = listeners.get(profile.getTenantId());
if (tenantListeners != null) {
tenantListeners.forEach((id, listener) -> listener.accept(profile));
}
}
} }

12
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java

@ -55,17 +55,17 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
public TenantProfile get(TenantProfileId tenantProfileId) { public TenantProfile get(TenantProfileId tenantProfileId) {
TenantProfile profile = tenantProfilesMap.get(tenantProfileId); TenantProfile profile = tenantProfilesMap.get(tenantProfileId);
if (profile == null) { if (profile == null) {
profile = tenantProfilesMap.get(tenantProfileId); tenantProfileFetchLock.lock();
if (profile == null) { try {
tenantProfileFetchLock.lock(); profile = tenantProfilesMap.get(tenantProfileId);
try { if (profile == null) {
profile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenantProfileId); profile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenantProfileId);
if (profile != null) { if (profile != null) {
tenantProfilesMap.put(tenantProfileId, profile); tenantProfilesMap.put(tenantProfileId, profile);
} }
} finally {
tenantProfileFetchLock.unlock();
} }
} finally {
tenantProfileFetchLock.unlock();
} }
} }
return profile; return profile;

6
application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java

@ -19,13 +19,17 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache { public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache {
void put(DeviceProfile profile); void put(DeviceProfile profile);
void evict(DeviceProfileId id); void evict(TenantId tenantId, DeviceProfileId id);
void evict(DeviceId id); void evict(DeviceId id);
DeviceProfile find(DeviceProfileId deviceProfileId);
DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String deviceType);
} }

4
application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java

@ -15,11 +15,7 @@
*/ */
package org.thingsboard.server.service.profile; package org.thingsboard.server.service.profile;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;

59
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -23,11 +23,15 @@ import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -189,21 +193,51 @@ public class DefaultTbClusterService implements TbClusterService {
@Override @Override
public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) { public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) {
log.trace("[{}][{}] Processing device profile [{}] change event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName()); onEntityChange(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback);
TransportProtos.DeviceProfileUpdateMsg profileUpdateMsg = TransportProtos.DeviceProfileUpdateMsg.newBuilder() }
.setData(ByteString.copyFrom(encodingService.encode(deviceProfile))).build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileUpdateMsg(profileUpdateMsg).build(); @Override
broadcast(transportMsg); public void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback) {
onEntityChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback);
}
@Override
public void onTenantChange(Tenant tenant, TbQueueCallback callback) {
onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback);
}
@Override
public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) {
onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback);
} }
@Override @Override
public void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback) { public void onTenantProfileDelete(TenantProfile entity, TbQueueCallback callback) {
log.trace("[{}][{}] Processing device profile [{}] delete event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName()); onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback);
TransportProtos.DeviceProfileDeleteMsg profileDeleteMsg = TransportProtos.DeviceProfileDeleteMsg.newBuilder() }
.setProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits())
.setProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) @Override
public void onTenantDelete(Tenant entity, TbQueueCallback callback) {
onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback);
}
public <T extends HasName> void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) {
log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entity.getName());
TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder()
.setEntityType(entityid.getEntityType().name())
.setData(ByteString.copyFrom(encodingService.encode(entity))).build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build();
broadcast(transportMsg);
}
private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) {
log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name);
TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder()
.setEntityType(entityId.getEntityType().name())
.setEntityIdMSB(entityId.getId().getMostSignificantBits())
.setEntityIdLSB(entityId.getId().getLeastSignificantBits())
.build(); .build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileDeleteMsg(profileDeleteMsg).build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build();
broadcast(transportMsg); broadcast(transportMsg);
} }
@ -221,7 +255,8 @@ public class DefaultTbClusterService implements TbClusterService {
byte[] msgBytes = encodingService.encode(msg); byte[] msgBytes = encodingService.encode(msg);
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();
Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE));
if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)) { if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)
|| msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)) {
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
for (String serviceId : tbCoreServices) { for (String serviceId : tbCoreServices) {

13
application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java

@ -17,7 +17,8 @@ package org.thingsboard.server.service.queue;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -53,5 +54,13 @@ public interface TbClusterService {
void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback); void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback);
void onDeviceProfileDelete(DeviceProfile deviceProfileId, TbQueueCallback callback); void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback);
void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback);
void onTenantProfileDelete(TenantProfile tenantProfile, TbQueueCallback callback);
void onTenantChange(Tenant tenant, TbQueueCallback callback);
void onTenantDelete(Tenant tenant, TbQueueCallback callback);
} }

2
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -144,7 +144,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
if (actorMsg instanceof ComponentLifecycleMsg) { if (actorMsg instanceof ComponentLifecycleMsg) {
ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg; ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceProfileId(componentLifecycleMsg.getEntityId().getId())); deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId())); deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId()));
} }

22
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
@ -76,12 +78,15 @@ public abstract class AbstractOAuth2ClientMapper {
private final Lock userCreationLock = new ReentrantLock(); private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail());
if (user == null && !allowUserCreation) { if (user == null && !config.isAllowUserCreation()) {
throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail());
} }
@ -106,21 +111,28 @@ public abstract class AbstractOAuth2ClientMapper {
user.setFirstName(oauth2User.getFirstName()); user.setFirstName(oauth2User.getFirstName());
user.setLastName(oauth2User.getLastName()); user.setLastName(oauth2User.getLastName());
ObjectNode additionalInfo = objectMapper.createObjectNode();
if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) { if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) {
Optional<DashboardId> dashboardIdOpt = Optional<DashboardId> dashboardIdOpt =
user.getAuthority() == Authority.TENANT_ADMIN ? user.getAuthority() == Authority.TENANT_ADMIN ?
getDashboardId(tenantId, oauth2User.getDefaultDashboardName()) getDashboardId(tenantId, oauth2User.getDefaultDashboardName())
: getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName()); : getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName());
if (dashboardIdOpt.isPresent()) { if (dashboardIdOpt.isPresent()) {
ObjectNode additionalInfo = objectMapper.createObjectNode();
additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen()); additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen());
additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString()); additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString());
user.setAdditionalInfo(additionalInfo);
} }
} }
if (clientRegistration.getAdditionalInfo() != null &&
clientRegistration.getAdditionalInfo().has("providerName")) {
additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText());
}
user.setAdditionalInfo(additionalInfo);
user = userService.saveUser(user); user = userService.saveUser(user);
if (activateUser) { if (config.isActivateUser()) {
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode("")); userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode(""));
} }

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.auth.oauth2;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
@ -29,11 +30,12 @@ import java.util.Map;
public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
@Override @Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
Map<String, Object> attributes = token.getPrincipal().getAttributes(); Map<String, Object> attributes = token.getPrincipal().getAttributes();
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration);
} }
} }

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
@ -38,9 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@Override @Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration);
} }
private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java

@ -23,6 +23,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
@ -45,12 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private OAuth2Configuration oAuth2Configuration; private OAuth2Configuration oAuth2Configuration;
@Override @Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) {
OAuth2MapperConfig config = clientRegistration.getMapperConfig();
Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
Map<String, Object> attributes = token.getPrincipal().getAttributes(); Map<String, Object> attributes = token.getPrincipal().getAttributes();
OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser()); return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration);
} }
private synchronized String getEmail(String emailUrl, String oauth2Token) { private synchronized String getEmail(String emailUrl, String oauth2Token) {

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java

@ -16,9 +16,9 @@
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
public interface OAuth2ClientMapper { public interface OAuth2ClientMapper {
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config); SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration);
} }

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -74,7 +74,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
token.getPrincipal().getName()); token.getPrincipal().getName());
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
clientRegistration.getMapperConfig()); clientRegistration);
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
@ -85,4 +85,4 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
} }
} }
} }

63
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -28,6 +28,7 @@ import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
@ -45,21 +46,19 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceProvisionService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
@ -70,6 +69,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.profile.TbTenantProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@ -90,9 +90,7 @@ public class DefaultTransportApiService implements TransportApiService {
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
//TODO: Constructor dependencies; private final TbDeviceProfileCache deviceProfileCache;
private final DeviceProfileService deviceProfileService;
private final TenantService tenantService;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final DeviceService deviceService; private final DeviceService deviceService;
private final RelationService relationService; private final RelationService relationService;
@ -103,17 +101,15 @@ public class DefaultTransportApiService implements TransportApiService {
private final DataDecodingEncodingService dataDecodingEncodingService; private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService; private final DeviceProvisionService deviceProvisionService;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache,
TbTenantProfileCache tenantProfileCache, DeviceService deviceService, TbTenantProfileCache tenantProfileCache, DeviceService deviceService,
RelationService relationService, DeviceCredentialsService deviceCredentialsService, RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService) { DeviceProvisionService deviceProvisionService) {
this.deviceProfileService = deviceProfileService; this.deviceProfileCache = deviceProfileCache;
this.tenantService = tenantService;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
this.deviceService = deviceService; this.deviceService = deviceService;
this.relationService = relationService; this.relationService = relationService;
@ -143,11 +139,8 @@ public class DefaultTransportApiService implements TransportApiService {
} else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()), return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasGetTenantRoutingInfoRequestMsg()) { } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()), return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()),
@ -238,7 +231,7 @@ public class DefaultTransportApiService implements TransportApiService {
device.setName(requestMsg.getDeviceName()); device.setName(requestMsg.getDeviceName());
device.setType(requestMsg.getDeviceType()); device.setType(requestMsg.getDeviceType());
device.setCustomerId(gateway.getCustomerId()); device.setCustomerId(gateway.getCustomerId());
DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
device.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
@ -258,7 +251,7 @@ public class DefaultTransportApiService implements TransportApiService {
} }
GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder()
.setDeviceInfo(getDeviceInfoProto(device)); .setDeviceInfo(getDeviceInfoProto(device));
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId());
if (deviceProfile != null) { if (deviceProfile != null) {
builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
} else { } else {
@ -320,23 +313,21 @@ public class DefaultTransportApiService implements TransportApiService {
.build(); .build();
} }
private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) { private ListenableFuture<TransportApiResponseMsg> handle(GetEntityProfileRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); EntityType entityType = EntityType.valueOf(requestMsg.getEntityType());
UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB());
ListenableFuture<TenantProfile> tenantProfileFuture = Futures.immediateFuture(tenantProfileCache.get(tenantId)); ByteString data;
return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder() if (entityType.equals(EntityType.DEVICE_PROFILE)) {
.setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore()) DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid);
.setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService); DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId);
} data = ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile));
} else if (entityType.equals(EntityType.TENANT)) {
private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetDeviceProfileRequestMsg requestMsg) { TenantProfile tenantProfile = tenantProfileCache.get(new TenantId(entityUuid));
DeviceProfileId profileId = new DeviceProfileId(new UUID(requestMsg.getProfileIdMSB(), requestMsg.getProfileIdLSB())); data = ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile));
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, profileId); } else {
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() throw new RuntimeException("Invalid entity profile request: " + entityType);
.setGetDeviceProfileResponseMsg( }
TransportProtos.GetDeviceProfileResponseMsg.newBuilder() return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(GetEntityProfileResponseMsg.newBuilder().setData(data).build()).build());
.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)))
.build()).build());
} }
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
@ -348,7 +339,7 @@ public class DefaultTransportApiService implements TransportApiService {
try { try {
ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder(); ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder();
builder.setDeviceInfo(getDeviceInfoProto(device)); builder.setDeviceInfo(getDeviceInfoProto(device));
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId());
if (deviceProfile != null) { if (deviceProfile != null) {
builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
} else { } else {

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

@ -502,7 +502,7 @@ js:
remote: remote:
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}"
# Maximum time in seconds for black listed function to stay in the list. # Maximum time in seconds for black listed function to stay in 1:the list.
max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}" max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}"
stats: stats:
enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}" enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}"
@ -512,10 +512,6 @@ transport:
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
json: json:
# Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"

6
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java

@ -27,6 +27,6 @@ import java.util.Set;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class OAuth2ClientsDomainParams { public class OAuth2ClientsDomainParams {
private Set<DomainInfo> domainInfos; private List<DomainInfo> domainInfos;
private Set<ClientRegistrationDto> clientRegistrations; private List<ClientRegistrationDto> clientRegistrations;
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java

@ -16,6 +16,8 @@
package org.thingsboard.server.common.data.oauth2; package org.thingsboard.server.common.data.oauth2;
import lombok.*; import lombok.*;
import java.util.List;
import java.util.Set; import java.util.Set;
@EqualsAndHashCode @EqualsAndHashCode
@ -26,5 +28,5 @@ import java.util.Set;
@AllArgsConstructor @AllArgsConstructor
public class OAuth2ClientsParams { public class OAuth2ClientsParams {
private boolean enabled; private boolean enabled;
private Set<OAuth2ClientsDomainParams> domainsParams; private List<OAuth2ClientsDomainParams> domainsParams;
} }

4
common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java

@ -50,4 +50,8 @@ public class TbRateLimits {
return bucket.tryConsume(1); return bucket.tryConsume(1);
} }
public boolean tryConsume(long number) {
return bucket.tryConsume(number);
}
} }

26
common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2020 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.queue.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
public @interface TbTransportComponent {
}

47
common/queue/src/main/proto/queue.proto

@ -177,32 +177,26 @@ message GetOrCreateDeviceFromGatewayResponseMsg {
bytes profileBody = 2; bytes profileBody = 2;
} }
message GetTenantRoutingInfoRequestMsg { message GetEntityProfileRequestMsg {
int64 tenantIdMSB = 1; string entityType = 1;
int64 tenantIdLSB = 2; int64 entityIdMSB = 2;
} int64 entityIdLSB = 3;
message GetTenantRoutingInfoResponseMsg {
bool isolatedTbCore = 1;
bool isolatedTbRuleEngine = 2;
}
message GetDeviceProfileRequestMsg {
int64 profileIdMSB = 1;
int64 profileIdLSB = 2;
} }
message GetDeviceProfileResponseMsg { message GetEntityProfileResponseMsg {
bytes data = 1; string entityType = 1;
bytes data = 2;
} }
message DeviceProfileUpdateMsg { message EntityUpdateMsg {
bytes data = 1; string entityType = 1;
bytes data = 2;
} }
message DeviceProfileDeleteMsg { message EntityDeleteMsg {
int64 profileIdMSB = 1; string entityType = 1;
int64 profileIdLSB = 2; int64 entityIdMSB = 2;
int64 entityIdLSB = 3;
} }
message SessionCloseNotificationProto { message SessionCloseNotificationProto {
@ -482,8 +476,7 @@ message TransportApiRequestMsg {
ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1;
ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2;
GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3;
GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; GetEntityProfileRequestMsg entityProfileRequestMsg = 4;
GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5;
ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
} }
@ -492,9 +485,8 @@ message TransportApiRequestMsg {
message TransportApiResponseMsg { message TransportApiResponseMsg {
ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1;
GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6;
} }
/* Messages that are handled by ThingsBoard Core Service */ /* Messages that are handled by ThingsBoard Core Service */
@ -535,7 +527,8 @@ message ToTransportMsg {
AttributeUpdateNotificationMsg attributeUpdateNotification = 5; AttributeUpdateNotificationMsg attributeUpdateNotification = 5;
ToDeviceRpcRequestMsg toDeviceRequest = 6; ToDeviceRpcRequestMsg toDeviceRequest = 6;
ToServerRpcResponseMsg toServerResponse = 7; ToServerRpcResponseMsg toServerResponse = 7;
DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; /* For Tenant, TenantProfile and DeviceProfile */
DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; EntityUpdateMsg entityUpdateMsg = 8;
EntityDeleteMsg entityDeleteMsg = 9;
ProvisionDeviceResponseMsg provisionResponse = 10; ProvisionDeviceResponseMsg provisionResponse = 10;
} }

8
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -263,12 +263,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
try { try {
MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
} else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java → common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import java.util.Optional; import java.util.Optional;
public interface TransportProfileCache { public interface TransportDeviceProfileCache {
DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody); DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody);

12
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.limits.TransportRateLimitType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
@ -47,7 +49,7 @@ import java.util.concurrent.ScheduledExecutorService;
*/ */
public interface TransportService { public interface TransportService {
GetTenantRoutingInfoResponseMsg getRoutingInfo(GetTenantRoutingInfoRequestMsg msg); GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg);
void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg,
TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); TransportServiceCallback<ValidateDeviceCredentialsResponse> callback);
@ -64,12 +66,12 @@ public interface TransportService {
void process(ProvisionDeviceRequestMsg msg, void process(ProvisionDeviceRequestMsg msg,
TransportServiceCallback<ProvisionDeviceResponseMsg> callback); TransportServiceCallback<ProvisionDeviceResponseMsg> callback);
void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback);
void onProfileUpdate(DeviceProfile deviceProfile); void onProfileUpdate(DeviceProfile deviceProfile);
boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback);
boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits);
void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback);

38
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2020 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.transport;
import com.google.protobuf.ByteString;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
import java.util.Set;
public interface TransportTenantProfileCache {
TenantProfile get(TenantId tenantId);
TenantProfileUpdateResult put(ByteString profileBody);
boolean put(TenantId tenantId, TenantProfileId profileId);
Set<TenantId> remove(TenantProfileId profileId);
}

47
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2020 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.transport.limits;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
@Slf4j
@Component
public class DefaultTransportRateLimitFactory implements TransportRateLimitFactory {
private static final DummyTransportRateLimit ALWAYS_TRUE = new DummyTransportRateLimit();
@Override
public TransportRateLimit create(TransportRateLimitType type, Object configuration) {
if (!StringUtils.isEmpty(configuration)) {
try {
return new SimpleTransportRateLimit(new TbRateLimits(configuration.toString()), configuration.toString());
} catch (Exception e) {
log.warn("[{}] Failed to init rate limit with configuration: {}", type, configuration, e);
return ALWAYS_TRUE;
}
} else {
return ALWAYS_TRUE;
}
}
@Override
public TransportRateLimit createDefault(TransportRateLimitType type) {
return ALWAYS_TRUE;
}
}

133
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java

@ -0,0 +1,133 @@
/**
* Copyright © 2016-2020 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.transport.limits;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.TenantProfileData;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Service
@TbTransportComponent
@Slf4j
public class DefaultTransportRateLimitService implements TransportRateLimitService {
private final ConcurrentMap<TenantId, TransportRateLimit[]> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, TransportRateLimit[]> perDeviceLimits = new ConcurrentHashMap<>();
private final TransportRateLimitFactory rateLimitFactory;
private final TransportTenantProfileCache tenantProfileCache;
public DefaultTransportRateLimitService(TransportRateLimitFactory rateLimitFactory, TransportTenantProfileCache tenantProfileCache) {
this.rateLimitFactory = rateLimitFactory;
this.tenantProfileCache = tenantProfileCache;
}
@Override
public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) {
TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId);
TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId);
for (TransportRateLimitType limitType : limits) {
TransportRateLimit rateLimit;
if (limitType.isTenantLevel()) {
rateLimit = tenantLimits[limitType.ordinal()];
} else {
rateLimit = deviceLimits[limitType.ordinal()];
}
if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) {
return limitType;
}
}
return null;
}
@Override
public void update(TenantProfileUpdateResult update) {
TransportRateLimit[] newLimits = createTransportRateLimits(update.getProfile());
for (TenantId tenantId : update.getAffectedTenants()) {
mergeLimits(tenantId, newLimits);
}
}
@Override
public void update(TenantId tenantId) {
mergeLimits(tenantId, fetchProfileAndInit(tenantId));
}
@Override
public void remove(TenantId tenantId) {
perTenantLimits.remove(tenantId);
}
@Override
public void remove(DeviceId deviceId) {
perDeviceLimits.remove(deviceId);
}
private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) {
TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId);
if (oldRateLimits == null) {
perTenantLimits.put(tenantId, newRateLimits);
} else {
for (int i = 0; i < TransportRateLimitType.values().length; i++) {
TransportRateLimit newLimit = newRateLimits[i];
TransportRateLimit oldLimit = oldRateLimits[i];
if (newLimit != null && (oldLimit == null || !oldLimit.getConfiguration().equals(newLimit.getConfiguration()))) {
oldRateLimits[i] = newLimit;
}
}
}
}
private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) {
return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId)));
}
private TransportRateLimit[] createTransportRateLimits(TenantProfile tenantProfile) {
TenantProfileData profileData = tenantProfile.getProfileData();
TransportRateLimit[] rateLimits = new TransportRateLimit[TransportRateLimitType.values().length];
for (TransportRateLimitType type : TransportRateLimitType.values()) {
rateLimits[type.ordinal()] = rateLimitFactory.create(type, profileData.getProperties().get(type.getConfigurationKey()));
}
return rateLimits;
}
private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) {
TransportRateLimit[] limits = perTenantLimits.get(tenantId);
if (limits == null) {
limits = fetchProfileAndInit(tenantId);
perTenantLimits.put(tenantId, limits);
}
return limits;
}
private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) {
TransportRateLimit[] limits = perDeviceLimits.get(deviceId);
if (limits == null) {
limits = fetchProfileAndInit(tenantId);
perDeviceLimits.put(deviceId, limits);
}
return limits;
}
}

35
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2020 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.transport.limits;
public class DummyTransportRateLimit implements TransportRateLimit {
@Override
public String getConfiguration() {
return "";
}
@Override
public boolean tryConsume(long number) {
return true;
}
@Override
public boolean tryConsume() {
return true;
}
}

38
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2020 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.transport.limits;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
@RequiredArgsConstructor
public class SimpleTransportRateLimit implements TransportRateLimit {
private final TbRateLimits rateLimit;
@Getter
private final String configuration;
@Override
public boolean tryConsume() {
return rateLimit.tryConsume();
}
@Override
public boolean tryConsume(long number) {
return number <= 0 || rateLimit.tryConsume(number);
}
}

26
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2020 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.transport.limits;
public interface TransportRateLimit {
String getConfiguration();
boolean tryConsume();
boolean tryConsume(long number);
}

24
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.transport.limits;
public interface TransportRateLimitFactory {
TransportRateLimit create(TransportRateLimitType type, Object config);
TransportRateLimit createDefault(TransportRateLimitType type);
}

34
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2020 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.transport.limits;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
public interface TransportRateLimitService {
TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits);
void update(TenantProfileUpdateResult update);
void update(TenantId tenantId);
void remove(TenantId tenantId);
void remove(DeviceId deviceId);
}

47
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2020 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.transport.limits;
import lombok.Getter;
public enum TransportRateLimitType {
TENANT_MAX_MSGS("transport.tenant.msg", true, true),
TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true),
TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false),
DEVICE_MAX_MSGS("transport.device.msg", false, true),
DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true),
DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false);
@Getter
private final String configurationKey;
@Getter
private final boolean tenantLevel;
@Getter
private final boolean deviceLevel;
@Getter
private final boolean messageLevel;
@Getter
private final boolean dataPointLevel;
TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) {
this.configurationKey = configurationKey;
this.tenantLevel = tenantLevel;
this.deviceLevel = !tenantLevel;
this.messageLevel = messageLevel;
this.dataPointLevel = !messageLevel;
}
}

30
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2020 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.transport.profile;
import lombok.Data;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Set;
@Data
public class TenantProfileUpdateResult {
private final TenantProfile profile;
private final Set<TenantId> affectedTenants;
}

9
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java → common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java

@ -21,8 +21,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.transport.TransportProfileCache; import org.thingsboard.server.common.transport.TransportDeviceProfileCache;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -30,14 +31,14 @@ import java.util.concurrent.ConcurrentMap;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") @TbTransportComponent
public class DefaultTransportProfileCache implements TransportProfileCache { public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache {
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>(); private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>();
private final DataDecodingEncodingService dataDecodingEncodingService; private final DataDecodingEncodingService dataDecodingEncodingService;
public DefaultTransportProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { public DefaultTransportDeviceProfileCache(DataDecodingEncodingService dataDecodingEncodingService) {
this.dataDecodingEncodingService = dataDecodingEncodingService; this.dataDecodingEncodingService = dataDecodingEncodingService;
} }

192
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -29,25 +29,35 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.common.stats.MessagesStats;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportProfileCache; import org.thingsboard.server.common.transport.TransportDeviceProfileCache;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.limits.TransportRateLimit;
import org.thingsboard.server.common.transport.limits.TransportRateLimitService;
import org.thingsboard.server.common.transport.limits.TransportRateLimitType;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
@ -69,15 +79,14 @@ import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.provider.TbTransportQueueFactory;
import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.queue.util.TbTransportComponent;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -95,15 +104,9 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
@Slf4j @Slf4j
@Service @Service
@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") @TbTransportComponent
public class DefaultTransportService implements TransportService { public class DefaultTransportService implements TransportService {
@Value("${transport.rate_limits.enabled}")
private boolean rateLimitEnabled;
@Value("${transport.rate_limits.tenant}")
private String perTenantLimitsConf;
@Value("${transport.rate_limits.device}")
private String perDevicesLimitsConf;
@Value("${transport.sessions.inactivity_timeout}") @Value("${transport.sessions.inactivity_timeout}")
private long sessionInactivityTimeout; private long sessionInactivityTimeout;
@Value("${transport.sessions.report_timeout}") @Value("${transport.sessions.report_timeout}")
@ -119,7 +122,10 @@ public class DefaultTransportService implements TransportService {
private final PartitionService partitionService; private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final StatsFactory statsFactory; private final StatsFactory statsFactory;
private final TransportProfileCache transportProfileCache; private final TransportDeviceProfileCache deviceProfileCache;
private final TransportTenantProfileCache tenantProfileCache;
private final TransportRateLimitService rateLimitService;
private final DataDecodingEncodingService dataDecodingEncodingService;
protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate; protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate;
protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer; protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
@ -132,14 +138,11 @@ public class DefaultTransportService implements TransportService {
protected ScheduledExecutorService schedulerExecutor; protected ScheduledExecutorService schedulerExecutor;
protected ExecutorService transportCallbackExecutor; protected ExecutorService transportCallbackExecutor;
private ExecutorService mainConsumerExecutor;
private final ConcurrentMap<UUID, SessionMetaData> sessions = new ConcurrentHashMap<>(); private final ConcurrentMap<UUID, SessionMetaData> sessions = new ConcurrentHashMap<>();
private final Map<String, RpcRequestMetadata> toServerRpcPendingMap = new ConcurrentHashMap<>(); private final Map<String, RpcRequestMetadata> toServerRpcPendingMap = new ConcurrentHashMap<>();
//TODO 3.2: @ybondarenko Implement cleanup of this maps.
private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, TbRateLimits> perDeviceLimits = new ConcurrentHashMap<>();
private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
private volatile boolean stopped = false; private volatile boolean stopped = false;
public DefaultTransportService(TbServiceInfoProvider serviceInfoProvider, public DefaultTransportService(TbServiceInfoProvider serviceInfoProvider,
@ -147,22 +150,22 @@ public class DefaultTransportService implements TransportService {
TbQueueProducerProvider producerProvider, TbQueueProducerProvider producerProvider,
PartitionService partitionService, PartitionService partitionService,
StatsFactory statsFactory, StatsFactory statsFactory,
TransportProfileCache transportProfileCache) { TransportDeviceProfileCache deviceProfileCache,
TransportTenantProfileCache tenantProfileCache,
TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService) {
this.serviceInfoProvider = serviceInfoProvider; this.serviceInfoProvider = serviceInfoProvider;
this.queueProvider = queueProvider; this.queueProvider = queueProvider;
this.producerProvider = producerProvider; this.producerProvider = producerProvider;
this.partitionService = partitionService; this.partitionService = partitionService;
this.statsFactory = statsFactory; this.statsFactory = statsFactory;
this.transportProfileCache = transportProfileCache; this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache;
this.rateLimitService = rateLimitService;
this.dataDecodingEncodingService = dataDecodingEncodingService;
} }
@PostConstruct @PostConstruct
public void init() { public void init() {
if (rateLimitEnabled) {
//Just checking the configuration parameters
new TbRateLimits(perTenantLimitsConf);
new TbRateLimits(perDevicesLimitsConf);
}
this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer");
this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer");
this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer");
@ -177,6 +180,7 @@ public class DefaultTransportService implements TransportService {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceInfoProvider.getServiceId()); TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceInfoProvider.getServiceId());
transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportNotificationsConsumer.subscribe(Collections.singleton(tpi));
transportApiRequestTemplate.init(); transportApiRequestTemplate.init();
mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
mainConsumerExecutor.execute(() -> { mainConsumerExecutor.execute(() -> {
while (!stopped) { while (!stopped) {
try { try {
@ -208,10 +212,6 @@ public class DefaultTransportService implements TransportService {
@PreDestroy @PreDestroy
public void destroy() { public void destroy() {
if (rateLimitEnabled) {
perTenantLimits.clear();
perDeviceLimits.clear();
}
stopped = true; stopped = true;
if (transportNotificationsConsumer != null) { if (transportNotificationsConsumer != null) {
@ -232,7 +232,7 @@ public class DefaultTransportService implements TransportService {
} }
@Override @Override
public ScheduledExecutorService getSchedulerExecutor(){ public ScheduledExecutorService getSchedulerExecutor() {
return this.schedulerExecutor; return this.schedulerExecutor;
} }
@ -242,12 +242,12 @@ public class DefaultTransportService implements TransportService {
} }
@Override @Override
public TransportProtos.GetTenantRoutingInfoResponseMsg getRoutingInfo(TransportProtos.GetTenantRoutingInfoRequestMsg msg) { public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) {
TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg = TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg =
new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setGetTenantRoutingInfoRequestMsg(msg).build()); new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build());
try { try {
TbProtoQueueMsg<TransportApiResponseMsg> response = transportApiRequestTemplate.send(protoMsg).get(); TbProtoQueueMsg<TransportApiResponseMsg> response = transportApiRequestTemplate.send(protoMsg).get();
return response.getValue().getGetTenantRoutingInfoResponseMsg(); return response.getValue().getEntityProfileResponseMsg();
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -289,7 +289,7 @@ public class DefaultTransportService implements TransportService {
result.deviceInfo(tdi); result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody(); ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) { if (profileBody != null && !profileBody.isEmpty()) {
DeviceProfile profile = transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
if (transportType != DeviceTransportType.DEFAULT if (transportType != DeviceTransportType.DEFAULT
&& profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) {
log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType); log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType);
@ -315,7 +315,7 @@ public class DefaultTransportService implements TransportService {
result.deviceInfo(tdi); result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody(); ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) { if (profileBody != null && !profileBody.isEmpty()) {
result.deviceProfile(transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody)); result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody));
} }
} }
return result.build(); return result.build();
@ -339,8 +339,8 @@ public class DefaultTransportService implements TransportService {
log.trace("Processing msg: {}", requestMsg); log.trace("Processing msg: {}", requestMsg);
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setProvisionDeviceRequestMsg(requestMsg).build()); TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setProvisionDeviceRequestMsg(requestMsg).build());
ListenableFuture<ProvisionDeviceResponseMsg> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> ListenableFuture<ProvisionDeviceResponseMsg> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp ->
tmp.getValue().getProvisionDeviceResponseMsg() tmp.getValue().getProvisionDeviceResponseMsg()
, MoreExecutors.directExecutor()); , MoreExecutors.directExecutor());
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
} }
@ -364,7 +364,11 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
if (checkLimits(sessionInfo, msg, callback)) { int dataPoints = 0;
for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
dataPoints += tsKv.getKvCount();
}
if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) {
reportActivityInternal(sessionInfo); reportActivityInternal(sessionInfo);
TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
@ -385,7 +389,7 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
if (checkLimits(sessionInfo, msg, callback)) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) {
reportActivityInternal(sessionInfo); reportActivityInternal(sessionInfo);
TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
@ -575,38 +579,34 @@ public class DefaultTransportService implements TransportService {
sessions.remove(toSessionId(sessionInfo)); sessions.remove(toSessionId(sessionInfo));
} }
private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS};
private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values();
@Override @Override
public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) { public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
return checkLimits(sessionInfo, msg, callback, 0, DEFAULT);
}
@Override
public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
} }
if (!rateLimitEnabled) {
return true;
}
TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(tenantId, id -> new TbRateLimits(perTenantLimitsConf));
if (!rateLimits.tryConsume()) {
if (callback != null) {
callback.onError(new TbRateLimitsException(EntityType.TENANT));
}
if (log.isTraceEnabled()) {
log.trace("[{}][{}] Tenant level rate limit detected: {}", toSessionId(sessionInfo), tenantId, msg);
}
return false;
}
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
rateLimits = perDeviceLimits.computeIfAbsent(deviceId, id -> new TbRateLimits(perDevicesLimitsConf));
if (!rateLimits.tryConsume()) { TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits);
if (limit == null) {
return true;
} else {
if (callback != null) { if (callback != null) {
callback.onError(new TbRateLimitsException(EntityType.DEVICE)); callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE));
} }
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("[{}][{}] Device level rate limit detected: {}", toSessionId(sessionInfo), deviceId, msg); log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg);
} }
return false; return false;
} }
return true;
} }
protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) {
@ -637,16 +637,40 @@ public class DefaultTransportService implements TransportService {
deregisterSession(md.getSessionInfo()); deregisterSession(md.getSessionInfo());
} }
} else { } else {
if (toSessionMsg.hasDeviceProfileUpdateMsg()) { if (toSessionMsg.hasEntityUpdateMsg()) {
DeviceProfile deviceProfile = transportProfileCache.put(toSessionMsg.getDeviceProfileUpdateMsg().getData()); TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg();
if (deviceProfile != null) { EntityType entityType = EntityType.valueOf(msg.getEntityType());
onProfileUpdate(deviceProfile); if (EntityType.DEVICE_PROFILE.equals(entityType)) {
DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData());
if (deviceProfile != null) {
onProfileUpdate(deviceProfile);
}
} else if (EntityType.TENANT_PROFILE.equals(entityType)) {
TenantProfileUpdateResult update = tenantProfileCache.put(msg.getData());
rateLimitService.update(update);
} else if (EntityType.TENANT.equals(entityType)) {
Optional<Tenant> profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray());
if (profileOpt.isPresent()) {
Tenant tenant = profileOpt.get();
boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId());
if (updated) {
rateLimitService.update(tenant.getId());
}
}
}
} else if (toSessionMsg.hasEntityDeleteMsg()) {
TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg();
EntityType entityType = EntityType.valueOf(msg.getEntityType());
UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB());
if (EntityType.DEVICE_PROFILE.equals(entityType)) {
deviceProfileCache.evict(new DeviceProfileId(new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB())));
} else if (EntityType.TENANT_PROFILE.equals(entityType)) {
tenantProfileCache.remove(new TenantProfileId(entityUuid));
} else if (EntityType.TENANT.equals(entityType)) {
rateLimitService.remove(new TenantId(entityUuid));
} else if (EntityType.DEVICE.equals(entityType)) {
rateLimitService.remove(new DeviceId(entityUuid));
} }
} else if (toSessionMsg.hasDeviceProfileDeleteMsg()) {
transportProfileCache.evict(new DeviceProfileId(new UUID(
toSessionMsg.getDeviceProfileDeleteMsg().getProfileIdMSB(),
toSessionMsg.getDeviceProfileDeleteMsg().getProfileIdLSB()
)));
} else { } else {
//TODO: should we notify the device actor about missed session? //TODO: should we notify the device actor about missed session?
log.debug("[{}] Missing session.", sessionId); log.debug("[{}] Missing session.", sessionId);
@ -654,38 +678,6 @@ public class DefaultTransportService implements TransportService {
} }
} }
@Override
public void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback) {
DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId);
if (deviceProfile != null) {
callback.onSuccess(deviceProfile);
} else {
log.trace("Processing device profile request: [{}]", deviceProfileId);
TransportProtos.GetDeviceProfileRequestMsg msg = TransportProtos.GetDeviceProfileRequestMsg.newBuilder()
.setProfileIdMSB(deviceProfileId.getId().getMostSignificantBits())
.setProfileIdLSB(deviceProfileId.getId().getLeastSignificantBits())
.build();
TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(),
TransportApiRequestMsg.newBuilder().setGetDeviceProfileRequestMsg(msg).build());
AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg),
response -> {
ByteString devProfileBody = response.getValue().getGetDeviceProfileResponseMsg().getData();
if (devProfileBody != null && !devProfileBody.isEmpty()) {
DeviceProfile profile = transportProfileCache.put(devProfileBody);
if (profile != null) {
callback.onSuccess(profile);
} else {
log.warn("Failed to decode device profile: {}", devProfileBody);
callback.onError(new IllegalArgumentException("Failed to decode device profile!"));
}
} else {
log.warn("Failed to find device profile: [{}]", deviceProfileId);
callback.onError(new IllegalArgumentException("Failed to find device profile!"));
}
}, callback::onError, transportCallbackExecutor);
}
}
@Override @Override
public void onProfileUpdate(DeviceProfile deviceProfile) { public void onProfileUpdate(DeviceProfile deviceProfile) {
long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits();
@ -750,7 +742,7 @@ public class DefaultTransportService implements TransportService {
private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) { private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) {
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId);
RuleChainId ruleChainId; RuleChainId ruleChainId;
if (deviceProfile == null) { if (deviceProfile == null) {
log.warn("[{}] Device profile is null!", deviceProfileId); log.warn("[{}] Device profile is null!", deviceProfileId);
@ -779,7 +771,7 @@ public class DefaultTransportService implements TransportService {
} }
} }
private class StatsCallback implements TbQueueCallback { private static class StatsCallback implements TbQueueCallback {
private final TbQueueCallback callback; private final TbQueueCallback callback;
private final MessagesStats stats; private final MessagesStats stats;

155
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java

@ -0,0 +1,155 @@
/**
* Copyright © 2016-2020 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.transport.service;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
import org.thingsboard.server.queue.util.TbTransportComponent;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Component
@TbTransportComponent
@Slf4j
public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache {
private final Lock tenantProfileFetchLock = new ReentrantLock();
private final ConcurrentMap<TenantProfileId, TenantProfile> profiles = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TenantProfileId> tenantIds = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantProfileId, Set<TenantId>> tenantProfileIds = new ConcurrentHashMap<>();
private final DataDecodingEncodingService dataDecodingEncodingService;
private TransportService transportService;
@Lazy
@Autowired
public void setTransportService(TransportService transportService) {
this.transportService = transportService;
}
public DefaultTransportTenantProfileCache(DataDecodingEncodingService dataDecodingEncodingService) {
this.dataDecodingEncodingService = dataDecodingEncodingService;
}
@Override
public TenantProfile get(TenantId tenantId) {
return getTenantProfile(tenantId);
}
@Override
public TenantProfileUpdateResult put(ByteString profileBody) {
Optional<TenantProfile> profileOpt = dataDecodingEncodingService.decode(profileBody.toByteArray());
if (profileOpt.isPresent()) {
TenantProfile newProfile = profileOpt.get();
log.trace("[{}] put: {}", newProfile.getId(), newProfile);
return new TenantProfileUpdateResult(newProfile, tenantProfileIds.get(newProfile.getId()));
} else {
log.warn("Failed to decode profile: {}", profileBody.toString());
return new TenantProfileUpdateResult(null, Collections.emptySet());
}
}
@Override
public boolean put(TenantId tenantId, TenantProfileId profileId) {
log.trace("[{}] put: {}", tenantId, profileId);
TenantProfileId oldProfileId = tenantIds.get(tenantId);
if (oldProfileId != null && !oldProfileId.equals(profileId)) {
tenantProfileIds.computeIfAbsent(oldProfileId, id -> ConcurrentHashMap.newKeySet()).remove(tenantId);
tenantIds.put(tenantId, profileId);
tenantProfileIds.computeIfAbsent(profileId, id -> ConcurrentHashMap.newKeySet()).add(tenantId);
return true;
} else {
return false;
}
}
@Override
public Set<TenantId> remove(TenantProfileId profileId) {
Set<TenantId> tenants = tenantProfileIds.remove(profileId);
if (tenants != null) {
tenants.forEach(tenantIds::remove);
}
profiles.remove(profileId);
return tenants;
}
private TenantProfile getTenantProfile(TenantId tenantId) {
TenantProfile profile = null;
TenantProfileId tenantProfileId = tenantIds.get(tenantId);
if (tenantProfileId != null) {
profile = profiles.get(tenantProfileId);
}
if (profile == null) {
tenantProfileFetchLock.lock();
try {
tenantProfileId = tenantIds.get(tenantId);
if (tenantProfileId != null) {
profile = profiles.get(tenantProfileId);
}
if (profile == null) {
TransportProtos.GetEntityProfileRequestMsg msg = TransportProtos.GetEntityProfileRequestMsg.newBuilder()
.setEntityType(EntityType.TENANT.name())
.setEntityIdMSB(tenantId.getId().getMostSignificantBits())
.setEntityIdLSB(tenantId.getId().getLeastSignificantBits())
.build();
TransportProtos.GetEntityProfileResponseMsg routingInfo = transportService.getRoutingInfo(msg);
Optional<TenantProfile> profileOpt = dataDecodingEncodingService.decode(routingInfo.getData().toByteArray());
if (profileOpt.isPresent()) {
profile = profileOpt.get();
TenantProfile existingProfile = profiles.get(profile.getId());
if (existingProfile != null) {
profile = existingProfile;
} else {
profiles.put(profile.getId(), profile);
}
tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId);
tenantIds.put(tenantId, profile.getId());
} else {
log.warn("[{}] Can't decode tenant profile: {}", tenantId, routingInfo.getData());
throw new RuntimeException("Can't decode tenant profile!");
}
}
} finally {
tenantProfileFetchLock.unlock();
}
}
return profile;
}
}

24
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java

@ -16,14 +16,11 @@
package org.thingsboard.server.common.transport.service; package org.thingsboard.server.common.transport.service;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ -32,21 +29,16 @@ import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
@ConditionalOnExpression("'${service.type:null}'=='tb-transport'") @ConditionalOnExpression("'${service.type:null}'=='tb-transport'")
public class TransportTenantRoutingInfoService implements TenantRoutingInfoService { public class TransportTenantRoutingInfoService implements TenantRoutingInfoService {
private TransportService transportService; private TransportTenantProfileCache tenantProfileCache;
@Lazy public TransportTenantRoutingInfoService(TransportTenantProfileCache tenantProfileCache) {
@Autowired this.tenantProfileCache = tenantProfileCache;
public void setTransportService(TransportService transportService) {
this.transportService = transportService;
} }
@Override @Override
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
GetTenantRoutingInfoRequestMsg msg = GetTenantRoutingInfoRequestMsg.newBuilder() TenantProfile profile = tenantProfileCache.get(tenantId);
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) return new TenantRoutingInfo(tenantId, profile.isIsolatedTbCore(), profile.isIsolatedTbRuleEngine());
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.build();
GetTenantRoutingInfoResponseMsg routingInfo = transportService.getRoutingInfo(msg);
return new TenantRoutingInfo(tenantId, routingInfo.getIsolatedTbCore(), routingInfo.getIsolatedTbRuleEngine());
} }
} }

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java

@ -15,8 +15,6 @@
*/ */
package org.thingsboard.server.common.transport.util; package org.thingsboard.server.common.transport.util;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.Optional; import java.util.Optional;
public interface DataDecodingEncodingService { public interface DataDecodingEncodingService {

12
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java

@ -32,19 +32,19 @@ public class OAuth2Utils {
} }
public static OAuth2ClientsParams toOAuth2Params(List<ExtendedOAuth2ClientRegistrationInfo> extendedOAuth2ClientRegistrationInfos) { public static OAuth2ClientsParams toOAuth2Params(List<ExtendedOAuth2ClientRegistrationInfo> extendedOAuth2ClientRegistrationInfos) {
Map<OAuth2ClientRegistrationInfoId, Set<DomainInfo>> domainsByInfoId = new HashMap<>(); Map<OAuth2ClientRegistrationInfoId, List<DomainInfo>> domainsByInfoId = new LinkedHashMap<>();
Map<OAuth2ClientRegistrationInfoId, OAuth2ClientRegistrationInfo> infoById = new HashMap<>(); Map<OAuth2ClientRegistrationInfoId, OAuth2ClientRegistrationInfo> infoById = new LinkedHashMap<>();
for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) { for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) {
String domainName = extendedClientRegistrationInfo.getDomainName(); String domainName = extendedClientRegistrationInfo.getDomainName();
SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme(); SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme();
domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>()) domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new ArrayList<>())
.add(new DomainInfo(domainScheme, domainName)); .add(new DomainInfo(domainScheme, domainName));
infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo);
} }
Map<Set<DomainInfo>, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); Map<List<DomainInfo>, OAuth2ClientsDomainParams> domainParamsMap = new LinkedHashMap<>();
domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> {
domainParamsMap.computeIfAbsent(domainInfos, domainParamsMap.computeIfAbsent(domainInfos,
key -> new OAuth2ClientsDomainParams(key, new HashSet<>()) key -> new OAuth2ClientsDomainParams(key, new ArrayList<>())
) )
.getClientRegistrations() .getClientRegistrations()
.add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId))); .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId)));
@ -52,7 +52,7 @@ public class OAuth2Utils {
boolean enabled = extendedOAuth2ClientRegistrationInfos.stream() boolean enabled = extendedOAuth2ClientRegistrationInfos.stream()
.map(OAuth2ClientRegistrationInfo::isEnabled) .map(OAuth2ClientRegistrationInfo::isEnabled)
.findFirst().orElse(false); .findFirst().orElse(false);
return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values())); return new OAuth2ClientsParams(enabled, new ArrayList<>(domainParamsMap.values()));
} }
public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) { public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) {

93
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
@ -29,7 +30,7 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class BaseOAuth2ServiceTest extends AbstractServiceTest { public class BaseOAuth2ServiceTest extends AbstractServiceTest {
private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>()); private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new ArrayList<>());
@Autowired @Autowired
protected OAuth2Service oAuth2Service; protected OAuth2Service oAuth2Service;
@ -48,14 +49,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class) @Test(expected = DataValidationException.class)
public void testSaveHttpAndMixedDomainsTogether() { public void testSaveHttpAndMixedDomainsTogether() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
@ -67,14 +68,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class) @Test(expected = DataValidationException.class)
public void testSaveHttpsAndMixedDomainsTogether() { public void testSaveHttpsAndMixedDomainsTogether() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
@ -131,20 +132,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
Assert.assertNotNull(foundClientsParams); Assert.assertNotNull(foundClientsParams);
Assert.assertEquals(clientsParams, foundClientsParams); Assert.assertEquals(clientsParams, foundClientsParams);
OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build() .build()
@ -157,22 +158,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test @Test
public void testGetOAuth2Clients() { public void testGetOAuth2Clients() {
Set<ClientRegistrationDto> firstGroup = Sets.newHashSet( List<ClientRegistrationDto> firstGroup = Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
); );
Set<ClientRegistrationDto> secondGroup = Sets.newHashSet( List<ClientRegistrationDto> secondGroup = Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
); );
Set<ClientRegistrationDto> thirdGroup = Sets.newHashSet( List<ClientRegistrationDto> thirdGroup = Lists.newArrayList(
validClientRegistrationDto() validClientRegistrationDto()
); );
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
@ -180,14 +181,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
.clientRegistrations(firstGroup) .clientRegistrations(firstGroup)
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
)) ))
.clientRegistrations(secondGroup) .clientRegistrations(secondGroup)
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
)) ))
@ -285,15 +286,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test @Test
public void testGetOAuth2ClientsForHttpAndHttps() { public void testGetOAuth2ClientsForHttpAndHttps() {
Set<ClientRegistrationDto> firstGroup = Sets.newHashSet( List<ClientRegistrationDto> firstGroup = Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
); );
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build()
@ -335,25 +336,25 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test @Test
public void testGetDisabledOAuth2Clients() { public void testGetDisabledOAuth2Clients() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))
@ -374,35 +375,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test @Test
public void testFindAllClientRegistrationInfos() { public void testFindAllClientRegistrationInfos() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build() .build()
@ -423,35 +424,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
@Test @Test
public void testFindClientRegistrationById() { public void testFindClientRegistrationById() {
OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(),
DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto() validClientRegistrationDto()
)) ))
.build() .build()
@ -466,14 +467,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
} }
private OAuth2ClientsParams createDefaultClientsParams() { private OAuth2ClientsParams createDefaultClientsParams() {
return new OAuth2ClientsParams(true, Sets.newHashSet( return new OAuth2ClientsParams(true, Lists.newArrayList(
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto(), validClientRegistrationDto(),
@ -481,11 +482,11 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
)) ))
.build(), .build(),
OAuth2ClientsDomainParams.builder() OAuth2ClientsDomainParams.builder()
.domainInfos(Sets.newHashSet( .domainInfos(Lists.newArrayList(
DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
)) ))
.clientRegistrations(Sets.newHashSet( .clientRegistrations(Lists.newArrayList(
validClientRegistrationDto(), validClientRegistrationDto(),
validClientRegistrationDto() validClientRegistrationDto()
)) ))

51
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java

@ -19,18 +19,40 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.*; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.mqtt.*; import io.netty.handler.codec.mqtt.MqttDecoder;
import io.netty.handler.codec.mqtt.MqttEncoder;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader;
import io.netty.handler.codec.mqtt.MqttMessageType;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
import io.netty.handler.codec.mqtt.MqttSubscribePayload;
import io.netty.handler.codec.mqtt.MqttTopicSubscription;
import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage;
import io.netty.handler.codec.mqtt.MqttUnsubscribePayload;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import java.util.*; import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -41,11 +63,11 @@ import java.util.concurrent.atomic.AtomicInteger;
final class MqttClientImpl implements MqttClient { final class MqttClientImpl implements MqttClient {
private final Set<String> serverSubscriptions = new HashSet<>(); private final Set<String> serverSubscriptions = new HashSet<>();
private final IntObjectHashMap<MqttPendingUnsubscription> pendingServerUnsubscribes = new IntObjectHashMap<>(); private final ConcurrentMap<Integer, MqttPendingUnsubscription> pendingServerUnsubscribes = new ConcurrentHashMap<>();
private final IntObjectHashMap<MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new IntObjectHashMap<>(); private final ConcurrentMap<Integer, MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new ConcurrentHashMap<>();
private final IntObjectHashMap<MqttPendingPublish> pendingPublishes = new IntObjectHashMap<>(); private final ConcurrentMap<Integer, MqttPendingPublish> pendingPublishes = new ConcurrentHashMap<>();
private final HashMultimap<String, MqttSubscription> subscriptions = HashMultimap.create(); private final HashMultimap<String, MqttSubscription> subscriptions = HashMultimap.create();
private final IntObjectHashMap<MqttPendingSubscription> pendingSubscriptions = new IntObjectHashMap<>(); private final ConcurrentMap<Integer, MqttPendingSubscription> pendingSubscriptions = new ConcurrentHashMap<>();
private final Set<String> pendingSubscribeTopics = new HashSet<>(); private final Set<String> pendingSubscribeTopics = new HashSet<>();
private final HashMultimap<MqttHandler, MqttSubscription> handlerToSubscribtion = HashMultimap.create(); private final HashMultimap<MqttHandler, MqttSubscription> handlerToSubscribtion = HashMultimap.create();
private final AtomicInteger nextMessageId = new AtomicInteger(1); private final AtomicInteger nextMessageId = new AtomicInteger(1);
@ -340,6 +362,7 @@ final class MqttClientImpl implements MqttClient {
MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId());
MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload);
MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos); MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos);
this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish);
ChannelFuture channelFuture = this.sendAndFlushPacket(message); ChannelFuture channelFuture = this.sendAndFlushPacket(message);
if (channelFuture != null) { if (channelFuture != null) {
@ -350,10 +373,12 @@ final class MqttClientImpl implements MqttClient {
} }
} }
if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) { if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) {
this.pendingPublishes.remove(pendingPublish.getMessageId());
pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
} else if (pendingPublish.isSent()) { } else if (pendingPublish.isSent()) {
this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish);
pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket);
} else {
this.pendingPublishes.remove(pendingPublish.getMessageId());
} }
return future; return future;
} }
@ -465,7 +490,7 @@ final class MqttClientImpl implements MqttClient {
} }
} }
IntObjectHashMap<MqttPendingSubscription> getPendingSubscriptions() { ConcurrentMap<Integer, MqttPendingSubscription> getPendingSubscriptions() {
return pendingSubscriptions; return pendingSubscriptions;
} }
@ -485,15 +510,15 @@ final class MqttClientImpl implements MqttClient {
return serverSubscriptions; return serverSubscriptions;
} }
IntObjectHashMap<MqttPendingUnsubscription> getPendingServerUnsubscribes() { ConcurrentMap<Integer, MqttPendingUnsubscription> getPendingServerUnsubscribes() {
return pendingServerUnsubscribes; return pendingServerUnsubscribes;
} }
IntObjectHashMap<MqttPendingPublish> getPendingPublishes() { ConcurrentMap<Integer, MqttPendingPublish> getPendingPublishes() {
return pendingPublishes; return pendingPublishes;
} }
IntObjectHashMap<MqttIncomingQos2Publish> getQos2PendingIncomingPublishes() { ConcurrentMap<Integer, MqttIncomingQos2Publish> getQos2PendingIncomingPublishes() {
return qos2PendingIncomingPublishes; return qos2PendingIncomingPublishes;
} }

392
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -38,10 +38,18 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
@ -50,6 +58,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
@ -60,28 +69,41 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@ -325,6 +347,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
} }
public Optional<AssetInfo> getAssetInfoById(AssetId assetId) {
try {
ResponseEntity<AssetInfo> asset = restTemplate.getForEntity(baseURL + "/api/asset/info/{assetId}", AssetInfo.class, assetId.getId());
return Optional.ofNullable(asset.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Asset saveAsset(Asset asset) { public Asset saveAsset(Asset asset) {
return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody();
} }
@ -390,6 +425,20 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
return assets.getBody(); return assets.getBody();
} }
public PageData<AssetInfo> getTenantAssetInfos(PageLink pageLink, String assetType) {
Map<String, String> params = new HashMap<>();
params.put("type", assetType);
addPageLinkToParam(params, pageLink);
ResponseEntity<PageData<AssetInfo>> assets = restTemplate.exchange(
baseURL + "/api/tenant/assetInfos?type={type}&" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<AssetInfo>>() {
},
params);
return assets.getBody();
}
public Optional<Asset> getTenantAsset(String assetName) { public Optional<Asset> getTenantAsset(String assetName) {
try { try {
ResponseEntity<Asset> asset = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, assetName); ResponseEntity<Asset> asset = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, assetName);
@ -419,6 +468,22 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
return assets.getBody(); return assets.getBody();
} }
public PageData<AssetInfo> getCustomerAssetInfos(CustomerId customerId, PageLink pageLink, String assetType) {
Map<String, String> params = new HashMap<>();
params.put("customerId", customerId.getId().toString());
params.put("type", assetType);
addPageLinkToParam(params, pageLink);
ResponseEntity<PageData<AssetInfo>> assets = restTemplate.exchange(
baseURL + "/api/customer/{customerId}/assetInfos?type={type}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<AssetInfo>>() {
},
params);
return assets.getBody();
}
public List<Asset> getAssetsByIds(List<AssetId> assetIds) { public List<Asset> getAssetsByIds(List<AssetId> assetIds) {
return restTemplate.exchange( return restTemplate.exchange(
baseURL + "/api/assets?assetIds={assetIds}", baseURL + "/api/assets?assetIds={assetIds}",
@ -593,11 +658,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
public Optional<JsonNode> activateUser(UserId userId, String password) { public Optional<JsonNode> activateUser(UserId userId, String password) {
return activateUser(userId, password, true);
}
public Optional<JsonNode> activateUser(UserId userId, String password, boolean sendActivationMail) {
ObjectNode activateRequest = objectMapper.createObjectNode(); ObjectNode activateRequest = objectMapper.createObjectNode();
activateRequest.put("activateToken", getActivateToken(userId)); activateRequest.put("activateToken", getActivateToken(userId));
activateRequest.put("password", password); activateRequest.put("password", password);
try { try {
ResponseEntity<JsonNode> jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate", activateRequest, JsonNode.class); ResponseEntity<JsonNode> jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate?sendActivationMail={sendActivationMail}", activateRequest, JsonNode.class, sendActivationMail);
return Optional.ofNullable(jsonNode.getBody()); return Optional.ofNullable(jsonNode.getBody());
} catch (HttpClientErrorException exception) { } catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
@ -936,8 +1005,25 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
} }
public Optional<DeviceInfo> getDeviceInfoById(DeviceId deviceId) {
try {
ResponseEntity<DeviceInfo> device = restTemplate.getForEntity(baseURL + "/api/device/info/{deviceId}", DeviceInfo.class, deviceId);
return Optional.ofNullable(device.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Device saveDevice(Device device) { public Device saveDevice(Device device) {
return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); return saveDevice(device, null);
}
public Device saveDevice(Device device, String accessToken) {
return restTemplate.postForEntity(baseURL + "/api/device?accessToken={accessToken}", device, Device.class, accessToken).getBody();
} }
public void deleteDevice(DeviceId deviceId) { public void deleteDevice(DeviceId deviceId) {
@ -1011,6 +1097,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody(); }, params).getBody();
} }
public PageData<DeviceInfo> getTenantDeviceInfos(String type, DeviceProfileId deviceProfileId, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("type", type);
params.put("deviceProfileId", deviceProfileId != null ? deviceProfileId.toString() : null);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/tenant/deviceInfos?type={type}&deviceProfileId={deviceProfileId}&" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<DeviceInfo>>() {
}, params).getBody();
}
public Optional<Device> getTenantDevice(String deviceName) { public Optional<Device> getTenantDevice(String deviceName) {
try { try {
ResponseEntity<Device> device = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, deviceName); ResponseEntity<Device> device = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, deviceName);
@ -1036,6 +1134,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody(); }, params).getBody();
} }
public PageData<DeviceInfo> getCustomerDeviceInfos(CustomerId customerId, String deviceType, DeviceProfileId deviceProfileId, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("customerId", customerId.toString());
params.put("type", deviceType);
params.put("deviceProfileId", deviceProfileId != null ? deviceProfileId.toString() : null);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/customer/{customerId}/devices?type={type}&deviceProfileId={deviceProfileId}&" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<DeviceInfo>>() {
}, params).getBody();
}
public List<Device> getDevicesByIds(List<DeviceId> deviceIds) { public List<Device> getDevicesByIds(List<DeviceId> deviceIds) {
return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}",
HttpMethod.GET, HttpMethod.GET,
@ -1074,6 +1185,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName); restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName);
} }
public Device assignDeviceToTenant(TenantId tenantId, DeviceId deviceId) {
return restTemplate.postForEntity(
baseURL + "/api/tenant/{tenantId}/device/{deviceId}",
HttpEntity.EMPTY, Device.class, tenantId, deviceId).getBody();
}
@Deprecated @Deprecated
public Device createDevice(String name, String type) { public Device createDevice(String name, String type) {
Device device = new Device(); Device device = new Device();
@ -1138,6 +1255,91 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
customerId.toString(), deviceId.toString()).getBody(); customerId.toString(), deviceId.toString()).getBody();
} }
public Optional<DeviceProfile> getDeviceProfileById(DeviceProfileId deviceProfileId) {
try {
ResponseEntity<DeviceProfile> deviceProfile = restTemplate.getForEntity(baseURL + "/api/deviceProfile/{deviceProfileId}", DeviceProfile.class, deviceProfileId);
return Optional.ofNullable(deviceProfile.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Optional<DeviceProfileInfo> getDeviceProfileInfoById(DeviceProfileId deviceProfileId) {
try {
ResponseEntity<DeviceProfileInfo> deviceProfileInfo = restTemplate.getForEntity(baseURL + "/api/deviceProfileInfo/{deviceProfileId}", DeviceProfileInfo.class, deviceProfileId);
return Optional.ofNullable(deviceProfileInfo.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public DeviceProfileInfo getDefaultDeviceProfileInfo() {
return restTemplate.getForEntity(baseURL + "/api/deviceProfileInfo/default", DeviceProfileInfo.class).getBody();
}
public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) {
return restTemplate.postForEntity(baseURL + "/api/deviceProfile", deviceProfile, DeviceProfile.class).getBody();
}
public void deleteDeviceProfile(DeviceProfileId deviceProfileId) {
restTemplate.delete(baseURL + "/api/deviceProfile/{deviceProfileId}", deviceProfileId);
}
public DeviceProfile setDefaultDeviceProfile(DeviceProfileId deviceProfileId) {
return restTemplate.postForEntity(
baseURL + "/api/deviceProfile/{deviceProfileId}/default",
HttpEntity.EMPTY, DeviceProfile.class, deviceProfileId).getBody();
}
public PageData<DeviceProfile> getTenantDevices(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/deviceProfiles?" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<DeviceProfile>>() {
}, params).getBody();
}
public PageData<DeviceProfileInfo> getDeviceProfileInfos(PageLink pageLink, DeviceTransportType deviceTransportType) {
Map<String, String> params = new HashMap<>();
params.put("deviceTransportType", deviceTransportType != null ? deviceTransportType.name() : null);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/deviceProfileInfos?deviceTransportType={deviceTransportType}&" + getUrlParams(pageLink),
HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<DeviceProfileInfo>>() {
}, params).getBody();
}
public Long countEntitiesByQuery(EntityCountQuery query) {
return restTemplate.postForObject(baseURL + "/api/entitiesQuery/count", query, Long.class);
}
public PageData<EntityData> findEntityDataByQuery(EntityDataQuery query) {
return restTemplate.exchange(
baseURL + "/api/entitiesQuery/find",
HttpMethod.POST, new HttpEntity<>(query),
new ParameterizedTypeReference<PageData<EntityData>>() {
}).getBody();
}
public PageData<AlarmData> findAlarmDataByQuery(AlarmDataQuery query) {
return restTemplate.exchange(
baseURL + "/api/alarmsQuery/find",
HttpMethod.POST, new HttpEntity<>(query),
new ParameterizedTypeReference<PageData<AlarmData>>() {
}).getBody();
}
public void saveRelation(EntityRelation relation) { public void saveRelation(EntityRelation relation) {
restTemplate.postForLocation(baseURL + "/api/relation", relation); restTemplate.postForLocation(baseURL + "/api/relation", relation);
} }
@ -1313,6 +1515,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
} }
public Optional<EntityViewInfo> getEntityViewInfoById(EntityViewId entityViewId) {
try {
ResponseEntity<EntityViewInfo> entityView = restTemplate.getForEntity(baseURL + "/api/entityView/info/{entityViewId}", EntityViewInfo.class, entityViewId);
return Optional.ofNullable(entityView.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public EntityView saveEntityView(EntityView entityView) { public EntityView saveEntityView(EntityView entityView) {
return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody();
} }
@ -1373,6 +1588,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody(); }, params).getBody();
} }
public PageData<EntityViewInfo> getCustomerEntityViewInfos(CustomerId customerId, String entityViewType, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("customerId", customerId.toString());
params.put("type", entityViewType);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/customer/{customerId}/entityViewInfos?type={type}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EntityViewInfo>>() {
}, params).getBody();
}
public PageData<EntityView> getTenantEntityViews(String entityViewType, PageLink pageLink) { public PageData<EntityView> getTenantEntityViews(String entityViewType, PageLink pageLink) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("type", entityViewType); params.put("type", entityViewType);
@ -1385,6 +1613,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody(); }, params).getBody();
} }
public PageData<EntityViewInfo> getTenantEntityViewInfos(String entityViewType, PageLink pageLink) {
Map<String, String> params = new HashMap<>();
params.put("type", entityViewType);
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/tenant/entityViewInfos?type={type}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EntityViewInfo>>() {
}, params).getBody();
}
public List<EntityView> findByQuery(EntityViewSearchQuery query) { public List<EntityView> findByQuery(EntityViewSearchQuery query) {
return restTemplate.exchange(baseURL + "/api/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference<List<EntityView>>() { return restTemplate.exchange(baseURL + "/api/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference<List<EntityView>>() {
}).getBody(); }).getBody();
@ -1437,8 +1677,41 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
HttpMethod.GET, HttpMethod.GET,
HttpEntity.EMPTY, HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<Event>>() { new ParameterizedTypeReference<PageData<Event>>() {
}, }, params).getBody();
params).getBody(); }
public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
return restTemplate.postForEntity(baseURL + "/api/oauth2/config/template", clientRegistrationTemplate, OAuth2ClientRegistrationTemplate.class).getBody();
}
public void deleteClientRegistrationTemplate(OAuth2ClientRegistrationTemplateId oAuth2ClientRegistrationTemplateId) {
restTemplate.delete(baseURL + "/api/oauth2/config/template/{clientRegistrationTemplateId}", oAuth2ClientRegistrationTemplateId);
}
public List<OAuth2ClientRegistrationTemplate> getClientRegistrationTemplates() {
return restTemplate.exchange(
baseURL + "/api/oauth2/config/template",
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<OAuth2ClientRegistrationTemplate>>() {
}).getBody();
}
public List<OAuth2ClientInfo> getOAuth2Clients() {
return restTemplate.exchange(
baseURL + "/api/noauth/oauth2Clients",
HttpMethod.POST,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<OAuth2ClientInfo>>() {
}).getBody();
}
public OAuth2ClientsParams getCurrentOAuth2Params() {
return restTemplate.getForEntity(baseURL + "/api/oauth2/config", OAuth2ClientsParams.class).getBody();
}
public OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params) {
return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody();
} }
public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) {
@ -1485,6 +1758,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody();
} }
public RuleChain saveRuleChain(DefaultRuleChainCreateRequest request) {
return restTemplate.postForEntity(baseURL + "/api/ruleChain/device/default", request, RuleChain.class).getBody();
}
public Optional<RuleChain> setRootRuleChain(RuleChainId ruleChainId) { public Optional<RuleChain> setRootRuleChain(RuleChainId ruleChainId) {
try { try {
ResponseEntity<RuleChain> ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId.getId()); ResponseEntity<RuleChain> ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId.getId());
@ -1544,6 +1821,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
} }
public RuleChainData exportRuleChains(int limit) {
return restTemplate.getForEntity(baseURL + "/api/ruleChains/export?limit=" + limit, RuleChainData.class).getBody();
}
public void importRuleChains(RuleChainData ruleChainData, boolean overwrite) {
restTemplate.postForLocation(baseURL + "/api/ruleChains/import?overwrite=" + overwrite, ruleChainData);
}
public List<String> getAttributeKeys(EntityId entityId) { public List<String> getAttributeKeys(EntityId entityId) {
return restTemplate.exchange( return restTemplate.exchange(
baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes", baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes",
@ -1805,6 +2090,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
} }
public Optional<TenantInfo> getTenantInfoById(TenantId tenantId) {
try {
ResponseEntity<TenantInfo> tenant = restTemplate.getForEntity(baseURL + "/api/tenant/info/{tenantId}", TenantInfo.class, tenantId);
return Optional.ofNullable(tenant.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Tenant saveTenant(Tenant tenant) { public Tenant saveTenant(Tenant tenant) {
return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody();
} }
@ -1824,6 +2122,81 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
}, params).getBody(); }, params).getBody();
} }
public PageData<TenantInfo> getTenantInfos(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/tenantInfos?" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<TenantInfo>>() {
}, params).getBody();
}
public Optional<TenantProfile> getTenantProfileById(TenantProfileId tenantProfileId) {
try {
ResponseEntity<TenantProfile> tenantProfile = restTemplate.getForEntity(baseURL + "/api/tenantProfile/{tenantProfileId}", TenantProfile.class, tenantProfileId);
return Optional.ofNullable(tenantProfile.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Optional<EntityInfo> getTenantProfileInfoById(TenantProfileId tenantProfileId) {
try {
ResponseEntity<EntityInfo> entityInfo = restTemplate.getForEntity(baseURL + "/api/tenantProfileInfo/{tenantProfileId}", EntityInfo.class, tenantProfileId);
return Optional.ofNullable(entityInfo.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public EntityInfo getDefaultTenantProfileInfo() {
return restTemplate.getForEntity(baseURL + "/api/tenantProfileInfo/default", EntityInfo.class).getBody();
}
public TenantProfile saveTenantProfile(TenantProfile tenantProfile) {
return restTemplate.postForEntity(baseURL + "/api/tenantProfile", tenantProfile, TenantProfile.class).getBody();
}
public void deleteTenantProfile(TenantProfileId tenantProfileId) {
restTemplate.delete(baseURL + "/api/tenantProfile/{tenantProfileId}", tenantProfileId);
}
public TenantProfile setDefaultTenantProfile(TenantProfileId tenantProfileId) {
return restTemplate.exchange(baseURL + "/api/tenantProfile/{tenantProfileId}/default", HttpMethod.POST, HttpEntity.EMPTY, TenantProfile.class, tenantProfileId).getBody();
}
public PageData<TenantProfile> getTenantProfiles(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/tenantProfiles" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<TenantProfile>>() {
}, params).getBody();
}
public PageData<EntityInfo> getTenantProfileInfos(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/tenantProfileInfos" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<EntityInfo>>() {
}, params).getBody();
}
public Optional<User> getUserById(UserId userId) { public Optional<User> getUserById(UserId userId) {
try { try {
ResponseEntity<User> user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId.getId()); ResponseEntity<User> user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId.getId());
@ -1870,6 +2243,17 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
restTemplate.delete(baseURL + "/api/user/{userId}", userId.getId()); restTemplate.delete(baseURL + "/api/user/{userId}", userId.getId());
} }
public PageData<User> getUsers(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return restTemplate.exchange(
baseURL + "/api/users" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<User>>() {
}, params).getBody();
}
public PageData<User> getTenantAdmins(TenantId tenantId, PageLink pageLink) { public PageData<User> getTenantAdmins(TenantId tenantId, PageLink pageLink) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("tenantId", tenantId.getId().toString()); params.put("tenantId", tenantId.getId().toString());

7
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceProfileCache.java

@ -18,8 +18,11 @@ package org.thingsboard.rule.engine.api;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import java.util.function.Consumer;
/** /**
* Created by ashvayka on 02.04.18. * Created by ashvayka on 02.04.18.
*/ */
@ -29,4 +32,8 @@ public interface RuleEngineDeviceProfileCache {
DeviceProfile get(TenantId tenantId, DeviceId deviceId); DeviceProfile get(TenantId tenantId, DeviceId deviceId);
void addListener(TenantId tenantId, EntityId listenerId, Consumer<DeviceProfile> listener);
void removeListener(TenantId tenantId, EntityId listenerId);
} }

5
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -20,6 +20,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -223,4 +224,8 @@ public interface TbContext {
RuleNodeState saveRuleNodeState(RuleNodeState state); RuleNodeState saveRuleNodeState(RuleNodeState state);
void clearRuleNodeStates(); void clearRuleNodeStates();
void addProfileListener(Consumer<DeviceProfile> listener);
void removeProfileListener();
} }

22
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.profile;
public enum AlarmEvalResult {
FALSE, NOT_YET_TRUE, TRUE;
}

30
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -123,17 +123,17 @@ class AlarmRuleState {
} }
} }
public boolean eval(DataSnapshot data) { public AlarmEvalResult eval(DataSnapshot data) {
boolean active = isActive(data.getTs()); boolean active = isActive(data.getTs());
switch (spec.getType()) { switch (spec.getType()) {
case SIMPLE: case SIMPLE:
return active && eval(alarmRule.getCondition(), data); return (active && eval(alarmRule.getCondition(), data)) ? AlarmEvalResult.TRUE : AlarmEvalResult.FALSE;
case DURATION: case DURATION:
return evalDuration(data, active); return evalDuration(data, active);
case REPEATING: case REPEATING:
return evalRepeating(data, active); return evalRepeating(data, active);
default: default:
return false; return AlarmEvalResult.FALSE;
} }
} }
@ -203,17 +203,17 @@ class AlarmRuleState {
} }
} }
private boolean evalRepeating(DataSnapshot data, boolean active) { private AlarmEvalResult evalRepeating(DataSnapshot data, boolean active) {
if (active && eval(alarmRule.getCondition(), data)) { if (active && eval(alarmRule.getCondition(), data)) {
state.setEventCount(state.getEventCount() + 1); state.setEventCount(state.getEventCount() + 1);
updateFlag = true; updateFlag = true;
return state.getEventCount() >= requiredRepeats; return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else { } else {
return false; return AlarmEvalResult.FALSE;
} }
} }
private boolean evalDuration(DataSnapshot data, boolean active) { private AlarmEvalResult evalDuration(DataSnapshot data, boolean active) {
if (active && eval(alarmRule.getCondition(), data)) { if (active && eval(alarmRule.getCondition(), data)) {
if (state.getLastEventTs() > 0) { if (state.getLastEventTs() > 0) {
if (data.getTs() > state.getLastEventTs()) { if (data.getTs() > state.getLastEventTs()) {
@ -226,24 +226,28 @@ class AlarmRuleState {
state.setDuration(0L); state.setDuration(0L);
updateFlag = true; updateFlag = true;
} }
return state.getDuration() > requiredDurationInMs; return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else { } else {
return false; return AlarmEvalResult.FALSE;
} }
} }
public boolean eval(long ts) { public AlarmEvalResult eval(long ts) {
switch (spec.getType()) { switch (spec.getType()) {
case SIMPLE: case SIMPLE:
case REPEATING: case REPEATING:
return false; return AlarmEvalResult.NOT_YET_TRUE;
case DURATION: case DURATION:
if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
long duration = state.getDuration() + (ts - state.getLastEventTs()); long duration = state.getDuration() + (ts - state.getLastEventTs());
return duration > requiredDurationInMs && isActive(ts); if (isActive(ts)) {
return duration > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else {
return AlarmEvalResult.FALSE;
}
} }
default: default:
return false; return AlarmEvalResult.FALSE;
} }
} }

38
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -72,7 +72,7 @@ class AlarmState {
return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval); return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval);
} }
public <T> boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, Boolean> evalFunction) { public <T> boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, AlarmEvalResult> evalFunction) {
boolean stateUpdate = false; boolean stateUpdate = false;
AlarmSeverity resultSeverity = null; AlarmSeverity resultSeverity = null;
log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); log.debug("[{}] processing update: {}", alarmDefinition.getId(), data);
@ -81,22 +81,28 @@ class AlarmState {
log.debug("[{}][{}] Update is not valid for current rule state", alarmDefinition.getId(), state.getSeverity()); log.debug("[{}][{}] Update is not valid for current rule state", alarmDefinition.getId(), state.getSeverity());
continue; continue;
} }
boolean evalResult = evalFunction.apply(state, data); AlarmEvalResult evalResult = evalFunction.apply(state, data);
stateUpdate |= state.checkUpdate(); stateUpdate |= state.checkUpdate();
if (evalResult) { if (AlarmEvalResult.TRUE.equals(evalResult)) {
resultSeverity = state.getSeverity(); resultSeverity = state.getSeverity();
break; break;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
state.clear();
stateUpdate |= state.checkUpdate();
} }
} }
if (resultSeverity != null) { if (resultSeverity != null) {
pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity)); TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity);
if (result != null) {
pushMsg(ctx, result);
}
} else if (currentAlarm != null && clearState != null) { } else if (currentAlarm != null && clearState != null) {
if (!validateUpdate(update, clearState)) { if (!validateUpdate(update, clearState)) {
log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId()); log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId());
return stateUpdate; return stateUpdate;
} }
Boolean evalResult = evalFunction.apply(clearState, data); AlarmEvalResult evalResult = evalFunction.apply(clearState, data);
if (evalResult) { if (AlarmEvalResult.TRUE.equals(evalResult)) {
stateUpdate |= clearState.checkUpdate(); stateUpdate |= clearState.checkUpdate();
for (AlarmRuleState state : createRulesSortedBySeverityDesc) { for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
state.clear(); state.clear();
@ -105,6 +111,9 @@ class AlarmState {
ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
currentAlarm = null; currentAlarm = null;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
clearState.clear();
stateUpdate |= clearState.checkUpdate();
} }
} }
return stateUpdate; return stateUpdate;
@ -183,13 +192,18 @@ class AlarmState {
// Maybe we should fetch alarm every time? // Maybe we should fetch alarm every time?
currentAlarm.setEndTs(System.currentTimeMillis()); currentAlarm.setEndTs(System.currentTimeMillis());
AlarmSeverity oldSeverity = currentAlarm.getSeverity(); AlarmSeverity oldSeverity = currentAlarm.getSeverity();
if (!oldSeverity.equals(severity)) { // Skip update if severity is decreased.
currentAlarm.setSeverity(severity); if (severity.ordinal() <= oldSeverity.ordinal()) {
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); if (!oldSeverity.equals(severity)) {
return new TbAlarmResult(false, false, true, false, currentAlarm); currentAlarm.setSeverity(severity);
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
return new TbAlarmResult(false, false, true, false, currentAlarm);
} else {
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
return new TbAlarmResult(false, true, false, false, currentAlarm);
}
} else { } else {
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); return null;
return new TbAlarmResult(false, true, false, false, currentAlarm);
} }
} else { } else {
currentAlarm = new Alarm(); currentAlarm = new Alarm();

64
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -57,16 +58,20 @@ import java.util.concurrent.TimeUnit;
) )
public class TbDeviceProfileNode implements TbNode { public class TbDeviceProfileNode implements TbNode {
private static final String PERIODIC_MSG_TYPE = "TbDeviceProfilePeriodicMsg"; private static final String PERIODIC_MSG_TYPE = "TbDeviceProfilePeriodicMsg";
private static final String PROFILE_UPDATE_MSG_TYPE = "TbDeviceProfileUpdateMsg";
private TbDeviceProfileNodeConfiguration config; private TbDeviceProfileNodeConfiguration config;
private RuleEngineDeviceProfileCache cache; private RuleEngineDeviceProfileCache cache;
private TbContext ctx;
private final Map<DeviceId, DeviceState> deviceStates = new ConcurrentHashMap<>(); private final Map<DeviceId, DeviceState> deviceStates = new ConcurrentHashMap<>();
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbDeviceProfileNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbDeviceProfileNodeConfiguration.class);
this.cache = ctx.getDeviceProfileCache(); this.cache = ctx.getDeviceProfileCache();
this.ctx = ctx;
scheduleAlarmHarvesting(ctx); scheduleAlarmHarvesting(ctx);
ctx.addProfileListener(this::onProfileUpdate);
if (config.isFetchAlarmRulesStateOnStart()) { if (config.isFetchAlarmRulesStateOnStart()) {
log.info("[{}] Fetching alarm rule state", ctx.getSelfId()); log.info("[{}] Fetching alarm rule state", ctx.getSelfId());
int fetchCount = 0; int fetchCount = 0;
@ -95,15 +100,14 @@ public class TbDeviceProfileNode implements TbNode {
} }
} }
/**
* TODO: Dynamic values evaluation;
*/
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
EntityType originatorType = msg.getOriginator().getEntityType(); EntityType originatorType = msg.getOriginator().getEntityType();
if (msg.getType().equals(PERIODIC_MSG_TYPE)) { if (msg.getType().equals(PERIODIC_MSG_TYPE)) {
scheduleAlarmHarvesting(ctx); scheduleAlarmHarvesting(ctx);
harvestAlarms(ctx, System.currentTimeMillis()); harvestAlarms(ctx, System.currentTimeMillis());
} else if (msg.getType().equals(PROFILE_UPDATE_MSG_TYPE)) {
updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData())));
} else { } else {
if (EntityType.DEVICE.equals(originatorType)) { if (EntityType.DEVICE.equals(originatorType)) {
DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); DeviceId deviceId = new DeviceId(msg.getOriginator().getId());
@ -119,36 +123,12 @@ public class TbDeviceProfileNode implements TbNode {
ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!")); ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!"));
} }
} }
} else if (EntityType.DEVICE_PROFILE.equals(originatorType)) {
log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), msg.getData());
if (msg.getType().equals("ENTITY_UPDATED")) {
DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class);
if (deviceProfile != null) {
for (DeviceState state : deviceStates.values()) {
if (deviceProfile.getId().equals(state.getProfileId())) {
state.updateProfile(ctx, deviceProfile);
}
}
}
}
ctx.tellSuccess(msg);
} else { } else {
ctx.tellSuccess(msg); ctx.tellSuccess(msg);
} }
} }
} }
public void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) {
DeviceState deviceState = deviceStates.get(deviceId);
if (deviceState != null) {
DeviceProfileId currentProfileId = deviceState.getProfileId();
Device device = JacksonUtil.fromString(deviceJson, Device.class);
if (!currentProfileId.equals(device.getDeviceProfileId())) {
deviceStates.remove(deviceId);
}
}
}
@Override @Override
public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) {
// Cleanup the cache for all entities that are no longer assigned to current server partitions // Cleanup the cache for all entities that are no longer assigned to current server partitions
@ -157,6 +137,7 @@ public class TbDeviceProfileNode implements TbNode {
@Override @Override
public void destroy() { public void destroy() {
ctx.removeProfileListener();
deviceStates.clear(); deviceStates.clear();
} }
@ -183,4 +164,33 @@ public class TbDeviceProfileNode implements TbNode {
} }
} }
protected void updateProfile(TbContext ctx, DeviceProfileId deviceProfileId) throws ExecutionException, InterruptedException {
DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceProfileId);
if (deviceProfile != null) {
log.debug("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile);
for (DeviceState state : deviceStates.values()) {
if (deviceProfile.getId().equals(state.getProfileId())) {
state.updateProfile(ctx, deviceProfile);
}
}
} else {
log.debug("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId);
}
}
protected void onProfileUpdate(DeviceProfile profile) {
ctx.tellSelf(TbMsg.newMsg(PROFILE_UPDATE_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, profile.getId().getId().toString()), 0L);
}
protected void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) {
DeviceState deviceState = deviceStates.get(deviceId);
if (deviceState != null) {
DeviceProfileId currentProfileId = deviceState.getProfileId();
Device device = JacksonUtil.fromString(deviceJson, Device.class);
if (!currentProfileId.equals(device.getDeviceProfileId())) {
deviceStates.remove(deviceId);
}
}
}
} }

4
transport/coap/src/main/resources/tb-coap-transport.yml

@ -49,10 +49,6 @@ transport:
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
json: json:
# Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"

4
transport/http/src/main/resources/tb-http-transport.yml

@ -42,10 +42,6 @@ transport:
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
json: json:
# Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"

4
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -71,10 +71,6 @@ transport:
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}"
json: json:
# Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"

4
ui-ngx/src/app/core/http/oauth2.service.ts

@ -41,4 +41,8 @@ export class OAuth2Service {
return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting, return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting,
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }
public getLoginProcessingUrl(config?: RequestConfig): Observable<string> {
return this.http.get<string>(`/api/oauth2/loginProcessingUrl`, defaultHttpOptionsFromConfig(config));
}
} }

5
ui-ngx/src/app/core/services/menu.service.ts

@ -191,6 +191,11 @@ export class MenuService {
name: 'admin.security-settings', name: 'admin.security-settings',
icon: 'security', icon: 'security',
path: '/settings/security-settings' path: '/settings/security-settings'
},
{
name: 'admin.oauth2.oauth2',
icon: 'security',
path: '/settings/oauth2'
} }
] ]
} }

17
ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html

@ -88,14 +88,20 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayout="row"> <div mat-dialog-actions fxLayout="row">
<div fxLayout="row" *ngIf="alarm$ | async; let alarm;"> <button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="close()" cdkFocusInitial>
{{ 'action.close' | translate }}
</button>
<span fxFlex></span>
<div fxLayout="row" *ngIf="alarm$ | async; let alarm;" fxLayoutGap="8px">
<button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK || <button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK ||
alarm.status === alarmStatuses.CLEARED_UNACK)" alarm.status === alarmStatuses.CLEARED_UNACK)"
mat-raised-button mat-raised-button
color="primary" color="primary"
type="button" type="button"
(click)="acknowledge()" (click)="acknowledge()"
style="margin-right: 20px;"
[disabled]="(isLoading$ | async)"> [disabled]="(isLoading$ | async)">
{{ 'alarm.acknowledge' | translate }} {{ 'alarm.acknowledge' | translate }}
</button> </button>
@ -109,12 +115,5 @@
{{ 'alarm.clear' | translate }} {{ 'alarm.clear' | translate }}
</button> </button>
</div> </div>
<span fxFlex></span>
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="close()" cdkFocusInitial>
{{ 'action.close' | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.html

@ -59,16 +59,16 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || entityAliasFormGroup.invalid || !entityAliasFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.save') | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || entityAliasFormGroup.invalid || !entityAliasFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.save') | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html

@ -95,11 +95,6 @@
{{ 'alias.add' | translate }} {{ 'alias.add' | translate }}
</button> </button>
<span fxFlex></span> <span fxFlex></span>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || entityAliasesFormGroup.invalid || !entityAliasesFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -107,5 +102,10 @@
cdkFocusInitial> cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || entityAliasesFormGroup.invalid || !entityAliasesFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html

@ -44,16 +44,16 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty">
{{ 'action.add' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty">
{{ 'action.add' | translate }}
</button>
</div> </div>
</form> </form>

15
ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html

@ -55,21 +55,22 @@
</mat-radio-group> </mat-radio-group>
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayout="row">
<mat-checkbox formControlName="openDashboard" <mat-checkbox formControlName="openDashboard"
style="margin-bottom: 0; padding-right: 20px;"> style="margin-bottom: 0;">
{{ 'dashboard.open-dashboard' | translate }} {{ 'dashboard.open-dashboard' | translate }}
</mat-checkbox> </mat-checkbox>
<button mat-raised-button color="primary" <span fxFlex></span>
type="submit"
[disabled]="(isLoading$ | async) || addWidgetFormGroup.invalid || !addWidgetFormGroup.dirty">
{{ 'action.add' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || addWidgetFormGroup.invalid || !addWidgetFormGroup.dirty">
{{ 'action.add' | translate }}
</button>
</div> </div>
</form> </form>

1
ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html

@ -26,7 +26,6 @@
<div fxLayout="row" class="tb-panel-actions"> <div fxLayout="row" class="tb-panel-actions">
<span fxFlex></span> <span fxFlex></span>
<button mat-button color="primary" <button mat-button color="primary"
style="margin-right: 20px;"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>

10
ui-ngx/src/app/modules/home/components/dashboard/select-target-state-dialog.component.html

@ -40,11 +40,6 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || stateFormGroup.invalid">
{{ 'action.select' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -52,5 +47,10 @@
cdkFocusInitial> cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || stateFormGroup.invalid">
{{ 'action.select' | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html

@ -33,11 +33,6 @@
<tb-anchor #entityDetailsForm></tb-anchor> <tb-anchor #entityDetailsForm></tb-anchor>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty">
{{ 'action.add' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
cdkFocusInitial cdkFocusInitial
@ -45,5 +40,10 @@
(click)="cancel()"> (click)="cancel()">
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty">
{{ 'action.add' | translate }}
</button>
</div> </div>
</form> </form>

1
ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html

@ -31,7 +31,6 @@
<div mat-dialog-actions fxLayout="row"> <div mat-dialog-actions fxLayout="row">
<span fxFlex></span> <span fxFlex></span>
<button mat-button color="primary" <button mat-button color="primary"
style="margin-right: 20px;"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
[mat-dialog-close]="false" cdkFocusInitial> [mat-dialog-close]="false" cdkFocusInitial>

12
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html

@ -46,12 +46,6 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!data.readonly"
type="submit"
[disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.update') | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -59,5 +53,11 @@
cdkFocusInitial> cdkFocusInitial>
{{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
</button> </button>
<button mat-raised-button color="primary"
*ngIf="!data.readonly"
type="submit"
[disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.update') | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html

@ -55,16 +55,16 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.update') | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.update') | translate }}
</button>
</div> </div>
</form> </form>

2
ui-ngx/src/app/modules/home/components/filter/filter-text.component.html

@ -15,5 +15,5 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass}" <div class="tb-filter-text" [ngClass]="{disabled: disabled, required: requiredClass, nowrap: nowrap}"
[innerHTML]="filterText"></div> [innerHTML]="filterText"></div>

5
ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss

@ -21,6 +21,11 @@
} }
&.required { &.required {
color: #f44336; color: #f44336;
padding: 0 4px;
}
&.nowrap {
white-space: nowrap;
overflow: hidden;
} }
} }
} }

3
ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts

@ -54,6 +54,9 @@ export class FilterTextComponent implements ControlValueAccessor, OnInit {
@Input() @Input()
addFilterPrompt = this.translate.instant('filter.add-filter-prompt'); addFilterPrompt = this.translate.instant('filter.add-filter-prompt');
@Input()
nowrap = false;
requiredClass = false; requiredClass = false;
private filterText: string; private filterText: string;

12
ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html

@ -46,12 +46,6 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!data.readonly"
type="submit"
[disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -59,5 +53,11 @@
cdkFocusInitial> cdkFocusInitial>
{{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
</button> </button>
<button mat-raised-button color="primary"
*ngIf="!data.readonly"
type="submit"
[disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html

@ -88,11 +88,6 @@
{{ 'filter.add' | translate }} {{ 'filter.add' | translate }}
</button> </button>
<span fxFlex></span> <span fxFlex></span>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || filtersFormGroup.invalid || !filtersFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -100,5 +95,10 @@
cdkFocusInitial> cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || filtersFormGroup.invalid || !filtersFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div> </div>
</form> </form>

12
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html

@ -79,12 +79,6 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
*ngIf="!data.readonly"
[disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
{{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -92,5 +86,11 @@
cdkFocusInitial> cdkFocusInitial>
{{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
*ngIf="!data.readonly"
[disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
{{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html

@ -65,16 +65,16 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || userFilterFormGroup.invalid || !userFilterFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || userFilterFormGroup.invalid || !userFilterFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
</div> </div>
</form> </form>

10
ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html

@ -43,16 +43,16 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || importFormGroup.invalid || !importFormGroup.dirty">
{{ 'action.import' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || importFormGroup.invalid || !importFormGroup.dirty">
{{ 'action.import' | translate }}
</button>
</div> </div>
</form> </form>

45
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div style="min-width: 1000px;"> <div>
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>device-profile.add</h2> <h2 translate>device-profile.add</h2>
<span fxFlex></span> <span fxFlex></span>
@ -106,28 +106,25 @@
</mat-step> </mat-step>
</mat-horizontal-stepper> </mat-horizontal-stepper>
</div> </div>
<div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;"> <div mat-dialog-actions fxLayout="row">
<div fxFlex fxLayout="row" fxLayoutAlign="end"> <button mat-stroked-button *ngIf="selectedIndex > 0"
<button mat-raised-button [disabled]="(isLoading$ | async)"
*ngIf="showNext" (click)="previousStep()">{{ 'action.back' | translate }}</button>
[disabled]="(isLoading$ | async)" <span fxFlex></span>
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button> <button mat-stroked-button
</div> color="primary"
<div fxFlex fxLayout="row"> *ngIf="showNext"
<button mat-button [disabled]="(isLoading$ | async)"
color="primary" (click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
[disabled]="(isLoading$ | async)" </div>
(click)="cancel()">{{ 'action.cancel' | translate }}</button> <mat-divider></mat-divider>
<span fxFlex></span> <div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end">
<div fxLayout="row wrap" fxLayoutGap="8px"> <button mat-button
<button mat-raised-button *ngIf="selectedIndex > 0" [disabled]="(isLoading$ | async)"
[disabled]="(isLoading$ | async)" (click)="cancel()">{{ 'action.cancel' | translate }}</button>
(click)="previousStep()">{{ 'action.back' | translate }}</button> <button mat-raised-button
<button mat-raised-button [disabled]="(isLoading$ | async)"
[disabled]="(isLoading$ | async)" color="primary"
color="primary" (click)="add()">{{ 'action.add' | translate }}</button>
(click)="add()">{{ 'action.add' | translate }}</button>
</div>
</div>
</div> </div>
</div> </div>

4
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss

@ -28,7 +28,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
padding: 24px 24px 8px !important; padding: 0 !important;
.mat-stepper-horizontal { .mat-stepper-horizontal {
display: flex; display: flex;
@ -45,7 +45,7 @@
} }
} }
.mat-horizontal-content-container { .mat-horizontal-content-container {
height: 350px; height: 530px;
max-height: 100%; max-height: 100%;
width: 100%;; width: 100%;;
overflow-y: auto; overflow-y: auto;

12
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html

@ -108,17 +108,17 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!readonly"
type="submit"
[disabled]="(isLoading$ | async) || conditionFormGroup.invalid || !conditionFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ (readonly ? 'action.close' : 'action.cancel') | translate }} {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
</button> </button>
<button mat-raised-button color="primary"
*ngIf="!readonly"
type="submit"
[disabled]="(isLoading$ | async) || conditionFormGroup.invalid || !conditionFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div> </div>
</form> </form>

25
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html

@ -15,25 +15,22 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="column" fxFlex [formGroup]="alarmRuleConditionFormGroup"> <div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" style="min-width: 0;">
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;">
<label class="tb-title" translate>device-profile.condition</label>
<span fxFlex></span>
<a mat-button color="primary"
type="button"
(click)="openFilterDialog($event)"
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
matTooltipPosition="above">
{{ (disabled ? 'action.view' : (conditionSet() ? 'action.edit' : 'action.add')) | translate }}
</a>
</div>
<div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)"> <div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)">
<tb-filter-text formControlName="condition" <tb-filter-text formControlName="condition"
[nowrap]="true"
required required
addFilterPrompt="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}"> addFilterPrompt="{{'device-profile.enter-alarm-rule-condition-prompt' | translate}}">
</tb-filter-text> </tb-filter-text>
<span class="tb-alarm-rule-condition-spec" [ngClass]="{disabled: this.disabled}" [innerHTML]="specText"> <span *ngIf="specText" class="tb-alarm-rule-condition-spec" [ngClass]="{disabled: this.disabled}" [innerHTML]="specText">
</span> </span>
</div> </div>
<button mat-icon-button
[color]="conditionSet() ? 'primary' : 'warn'"
type="button"
(click)="openFilterDialog($event)"
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
matTooltipPosition="above">
<mat-icon>{{ disabled ? 'visibility' : (conditionSet() ? 'edit' : 'add') }}</mat-icon>
</button>
</div> </div>

7
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss

@ -22,13 +22,10 @@
} }
.tb-alarm-rule-condition { .tb-alarm-rule-condition {
cursor: pointer; cursor: pointer;
min-width: 0;
.tb-alarm-rule-condition-spec { .tb-alarm-rule-condition-spec {
margin-top: 1em; opacity: 0.7;
line-height: 1.8em;
padding: 4px; padding: 4px;
&.disabled {
opacity: 0.7;
}
} }
} }
} }

45
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html

@ -16,36 +16,23 @@
--> -->
<div fxLayout="column" [formGroup]="alarmRuleFormGroup"> <div fxLayout="column" [formGroup]="alarmRuleFormGroup">
<tb-alarm-rule-condition fxFlex class="row" <tb-alarm-rule-condition formControlName="condition">
formControlName="condition">
</tb-alarm-rule-condition> </tb-alarm-rule-condition>
<mat-divider class="row"></mat-divider> <tb-alarm-schedule-info formControlName="schedule">
<tb-alarm-schedule-info fxFlex class="row"
formControlName="schedule">
</tb-alarm-schedule-info> </tb-alarm-schedule-info>
<mat-divider class="row"></mat-divider> <div *ngIf="!disabled || alarmRuleFormGroup.get('alarmDetails').value" fxLayout="row" fxLayoutAlign="start center">
<div fxLayout="column" fxFlex class="tb-alarm-rule-details row"> <span class="tb-alarm-rule-details title" (click)="openEditDetailsDialog($event)">
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> {{ alarmRuleFormGroup.get('alarmDetails').value ? ('device-profile.alarm-rule-details' | translate) + ': ' : ('device-profile.add-alarm-rule-details' | translate) }}
<label class="tb-title" translate>device-profile.alarm-rule-details</label> </span>
<span fxFlex></span> <span *ngIf="alarmRuleFormGroup.get('alarmDetails').value" class="tb-alarm-rule-details"
<a mat-button color="primary" (click)="openEditDetailsDialog($event)"
*ngIf="!disabled" [innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></span>
type="button" <button mat-icon-button color="primary"
(click)="openEditDetailsDialog($event)" type="button"
matTooltip="{{ 'action.edit' | translate }}" (click)="openEditDetailsDialog($event)"
matTooltipPosition="above"> matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
{{ 'action.edit' | translate }} matTooltipPosition="above">
</a> <mat-icon>{{ disabled ? 'visibility' : (alarmRuleFormGroup.get('alarmDetails').value ? 'edit' : 'add') }}</mat-icon>
</div> </button>
<div fxLayout="row" fxLayoutAlign="start start">
<div class="tb-alarm-rule-details-content" [ngClass]="{disabled: this.disabled, collapsed: !this.expandAlarmDetails}"
(click)="!disabled ? openEditDetailsDialog($event) : {}"
fxFlex [innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></div>
<a mat-button color="primary"
type="button"
(click)="expandAlarmDetails = !expandAlarmDetails">
{{ (expandAlarmDetails ? 'action.hide' : 'action.read-more') | translate }}
</a>
</div>
</div> </div>
</div> </div>

30
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss

@ -14,31 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
:host { :host {
min-width: 0;
.row { .row {
margin-top: 1em; margin-top: 1em;
} }
.tb-alarm-rule-details { .tb-alarm-rule-details {
a.mat-button { padding: 4px;
&:hover, &:focus { cursor: pointer;
border-bottom: none; overflow: hidden;
} white-space: nowrap;
} text-overflow: ellipsis;
.tb-alarm-rule-details-content { &.title {
min-height: 33px; opacity: 0.7;
overflow: hidden; overflow: visible;
white-space: pre;
line-height: 1.8em;
padding: 4px;
cursor: pointer;
&.collapsed {
max-height: 33px;
white-space: nowrap;
text-overflow: ellipsis;
}
&.disabled {
opacity: 0.7;
cursor: auto;
}
} }
} }
} }

3
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts

@ -118,7 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value,
readonly: this.disabled
} }
}).afterClosed().subscribe((alarmDetails) => { }).afterClosed().subscribe((alarmDetails) => {
if (isDefinedAndNotNull(alarmDetails)) { if (isDefinedAndNotNull(alarmDetails)) {

12
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html

@ -37,17 +37,17 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
*ngIf="!readonly"
type="submit"
[disabled]="(isLoading$ | async) || alarmScheduleFormGroup.invalid || !alarmScheduleFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ (readonly ? 'action.close' : 'action.cancel') | translate }} {{ (readonly ? 'action.close' : 'action.cancel') | translate }}
</button> </button>
<button mat-raised-button color="primary"
*ngIf="!readonly"
type="submit"
[disabled]="(isLoading$ | async) || alarmScheduleFormGroup.invalid || !alarmScheduleFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div> </div>
</form> </form>

25
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html

@ -15,19 +15,16 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="column" fxFlex> <div fxLayout="row" fxLayoutAlign="start center" style="min-width: 0;">
<div fxLayout="row" fxLayoutAlign="start center" style="min-height: 40px;"> <span class="tb-alarm-rule-schedule title" (click)="openScheduleDialog($event)">{{('device-profile.schedule' | translate) + ': '}}</span>
<label class="tb-title" translate>device-profile.schedule</label> <span class="tb-alarm-rule-schedule" (click)="openScheduleDialog($event)"
<span fxFlex></span>
<a mat-button color="primary"
type="button"
(click)="openScheduleDialog($event)"
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
matTooltipPosition="above">
{{ (disabled ? 'action.view' : 'action.edit' ) | translate }}
</a>
</div>
<sapn class="tb-alarm-rule-schedule" [ngClass]="{disabled: this.disabled}" (click)="openScheduleDialog($event)"
[innerHTML]="scheduleText"> [innerHTML]="scheduleText">
</sapn> </span>
<button mat-icon-button color="primary"
type="button"
(click)="openScheduleDialog($event)"
matTooltip="{{ (disabled ? 'action.view' : 'action.edit') | translate }}"
matTooltipPosition="above">
<mat-icon>{{ disabled ? 'visibility' : 'edit' }}</mat-icon>
</button>
</div> </div>

10
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss

@ -21,14 +21,14 @@
} }
} }
.tb-alarm-rule-schedule { .tb-alarm-rule-schedule {
line-height: 1.8em;
padding: 4px; padding: 4px;
cursor: pointer; cursor: pointer;
&.disabled { overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&.title {
opacity: 0.7; opacity: 0.7;
} overflow: visible;
.nowrap {
white-space: nowrap;
} }
} }
} }

2
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts

@ -101,7 +101,7 @@ export class AlarmScheduleInfoComponent implements ControlValueAccessor, OnInit
for (const item of schedule.items) { for (const item of schedule.items) {
if (item.enabled) { if (item.enabled) {
if (this.scheduleText.length) { if (this.scheduleText.length) {
this.scheduleText += '<br/>'; this.scheduleText += ', ';
} }
this.scheduleText += this.translate.instant(dayOfWeekTranslations[item.dayOfWeek - 1]); this.scheduleText += this.translate.instant(dayOfWeekTranslations[item.dayOfWeek - 1]);
this.scheduleText += ' <b>' + getAlarmScheduleRangeText(utcTimestampToTimeOfDay(item.startsOn), this.scheduleText += ' <b>' + getAlarmScheduleRangeText(utcTimestampToTimeOfDay(item.startsOn),

8
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html

@ -19,7 +19,7 @@
<div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index; <div *ngFor="let createAlarmRuleControl of createAlarmRulesFormArray().controls; let $index = index;
last as isLast;" fxLayout="row" fxLayoutAlign="start center" last as isLast;" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl"> fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl">
<div class="create-alarm-rule" fxFlex fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="start"> <div class="create-alarm-rule" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start">
<mat-form-field class="severity mat-block" floatLabel="always" hideRequiredMarker> <mat-form-field class="severity mat-block" floatLabel="always" hideRequiredMarker>
<mat-label translate>alarm.severity</mat-label> <mat-label translate>alarm.severity</mat-label>
<mat-select formControlName="severity" <mat-select formControlName="severity"
@ -34,7 +34,7 @@
{{ 'device-profile.alarm-severity-required' | translate }} {{ 'device-profile.alarm-severity-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-divider></mat-divider> <mat-divider vertical></mat-divider>
<tb-alarm-rule formControlName="alarmRule" required fxFlex> <tb-alarm-rule formControlName="alarmRule" required fxFlex>
</tb-alarm-rule> </tb-alarm-rule>
</div> </div>
@ -47,9 +47,9 @@
<mat-icon>remove_circle_outline</mat-icon> <mat-icon>remove_circle_outline</mat-icon>
</button> </button>
</div> </div>
<div *ngIf="!createAlarmRulesFormArray().controls.length"> <div *ngIf="!createAlarmRulesFormArray().controls.length && !disabled">
<span translate fxLayoutAlign="center center" style="margin: 16px 0" <span translate fxLayoutAlign="center center" style="margin: 16px 0"
class="tb-prompt">device-profile.no-create-alarm-rules</span> class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
</div> </div>
<div fxLayout="row" *ngIf="!disabled"> <div fxLayout="row" *ngIf="!disabled">
<button mat-stroked-button color="primary" <button mat-stroked-button color="primary"

21
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts

@ -23,11 +23,11 @@ import {
FormControl, FormControl,
FormGroup, FormGroup,
NG_VALIDATORS, NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR, ValidationErrors,
Validator, Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { AlarmRule } from '@shared/models/device.models'; import { AlarmRule, alarmRuleValidator } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models'; import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models';
@ -141,10 +141,23 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
}; };
const createAlarmRulesArray = this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray; const createAlarmRulesArray = this.createAlarmRulesFormGroup.get('createAlarmRules') as FormArray;
createAlarmRulesArray.push(this.fb.group({ createAlarmRulesArray.push(this.fb.group({
severity: [null, Validators.required], severity: [this.getFirstUnusedSeverity(), Validators.required],
alarmRule: [createAlarmRule, Validators.required] alarmRule: [createAlarmRule, alarmRuleValidator]
})); }));
this.createAlarmRulesFormGroup.updateValueAndValidity(); this.createAlarmRulesFormGroup.updateValueAndValidity();
if (!this.createAlarmRulesFormGroup.valid) {
this.updateModel();
}
}
private getFirstUnusedSeverity(): AlarmSeverity {
for (const severityKey of Object.keys(AlarmSeverity)) {
const severity = AlarmSeverity[severityKey];
if (this.usedSeverities.indexOf(severity) === -1) {
return severity;
}
}
return null;
} }
public validate(c: FormControl) { public validate(c: FormControl) {

2
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html

@ -98,7 +98,7 @@
<mat-icon>remove_circle_outline</mat-icon> <mat-icon>remove_circle_outline</mat-icon>
</button> </button>
</div> </div>
<div *ngIf="!alarmFormGroup.get('clearRule').value"> <div *ngIf="disabled && !alarmFormGroup.get('clearRule').value">
<span translate fxLayoutAlign="center center" style="margin: 16px 0" <span translate fxLayoutAlign="center center" style="margin: 16px 0"
class="tb-prompt">device-profile.no-clear-alarm-rule</span> class="tb-prompt">device-profile.no-clear-alarm-rule</span>
</div> </div>

4
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts

@ -25,7 +25,7 @@ import {
Validator, Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { AlarmRule, DeviceProfileAlarm } from '@shared/models/device.models'; import { AlarmRule, DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
@ -92,7 +92,7 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
clearRule: [null], clearRule: [null],
propagate: [null], propagate: [null],
propagateRelationTypes: [null] propagateRelationTypes: [null]
}); }, { validators: deviceProfileAlarmValidator });
this.alarmFormGroup.valueChanges.subscribe(() => { this.alarmFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
}); });

9
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts

@ -30,7 +30,7 @@ import {
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DeviceProfileAlarm } from '@shared/models/device.models'; import { DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/device.models';
import { guid } from '@core/utils'; import { guid } from '@core/utils';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -141,7 +141,7 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
id: guid(), id: guid(),
alarmType: '', alarmType: '',
createRules: { createRules: {
empty: { CRITICAL: {
condition: { condition: {
condition: [] condition: []
} }
@ -149,8 +149,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
} }
}; };
const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray; const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray;
alarmsArray.push(this.fb.control(alarm, [Validators.required])); alarmsArray.push(this.fb.control(alarm, [deviceProfileAlarmValidator]));
this.deviceProfileAlarmsFormGroup.updateValueAndValidity(); this.deviceProfileAlarmsFormGroup.updateValueAndValidity();
if (!this.deviceProfileAlarmsFormGroup.valid) {
this.updateModel();
}
} }
public validate(c: FormControl) { public validate(c: FormControl) {

10
ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html

@ -38,16 +38,16 @@
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial> (click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button *ngIf="!data.readonly" mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || editDetailsFormGroup.invalid || !editDetailsFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div> </div>
</form> </form>

4
ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts

@ -27,6 +27,7 @@ import { TranslateService } from '@ngx-translate/core';
export interface EditAlarmDetailsDialogData { export interface EditAlarmDetailsDialogData {
alarmDetails: string; alarmDetails: string;
readonly: boolean;
} }
@Component({ @Component({
@ -57,6 +58,9 @@ export class EditAlarmDetailsDialogComponent extends DialogComponent<EditAlarmDe
this.editDetailsFormGroup = this.fb.group({ this.editDetailsFormGroup = this.fb.group({
alarmDetails: [this.alarmDetails] alarmDetails: [this.alarmDetails]
}); });
if (this.data.readonly) {
this.editDetailsFormGroup.disable();
}
} }
ngOnInit(): void { ngOnInit(): void {

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

Loading…
Cancel
Save