Commit e981edd83c6ddf5cfa0ec137a240b3ab2194790e

Authored by Andrii Shvaika
2 parents f157d07c 67ae7ef3

Merge remote-tracking branch 'origin/master'

Showing 25 changed files with 130 additions and 33 deletions
... ... @@ -73,7 +73,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
73 73 import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
74 74 import org.thingsboard.server.service.mail.MailExecutorService;
75 75 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
76   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  76 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
77 77 import org.thingsboard.server.service.queue.TbClusterService;
78 78 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
79 79 import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
... ...
... ... @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.Tenant;
30 30 import org.thingsboard.server.common.data.TenantProfile;
31 31 import org.thingsboard.server.common.data.id.EntityId;
32 32 import org.thingsboard.server.common.data.id.TenantId;
33   -import org.thingsboard.server.common.data.id.TenantProfileId;
34 33 import org.thingsboard.server.common.data.page.PageDataIterable;
35 34 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
36 35 import org.thingsboard.server.common.msg.MsgType;
... ... @@ -41,7 +40,7 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
41 40 import org.thingsboard.server.common.msg.queue.RuleEngineException;
42 41 import org.thingsboard.server.common.msg.queue.ServiceType;
43 42 import org.thingsboard.server.dao.tenant.TenantService;
44   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  43 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
45 44 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
46 45
47 46 import java.util.HashSet;
... ...
... ... @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
35 35 import org.thingsboard.server.common.data.audit.ActionType;
36 36 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
37 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
38   -import org.thingsboard.server.common.data.id.*;
39 38 import org.thingsboard.server.common.data.id.AlarmId;
40 39 import org.thingsboard.server.common.data.id.AssetId;
41 40 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -94,7 +93,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
94 93 import org.thingsboard.server.queue.util.TbCoreComponent;
95 94 import org.thingsboard.server.service.component.ComponentDiscoveryService;
96 95 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
97   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  96 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
98 97 import org.thingsboard.server.service.queue.TbClusterService;
99 98 import org.thingsboard.server.service.security.model.SecurityUser;
100 99 import org.thingsboard.server.service.security.permission.AccessControlService;
... ...
... ... @@ -392,6 +392,11 @@ public class TelemetryController extends BaseController {
392 392 if (attributes.isEmpty()) {
393 393 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
394 394 }
  395 + for (AttributeKvEntry attributeKvEntry: attributes) {
  396 + if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) {
  397 + return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST);
  398 + }
  399 + }
395 400 SecurityUser user = getCurrentUser();
396 401 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
397 402 tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<Void>() {
... ...
... ... @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
55 55 import org.thingsboard.server.queue.discovery.PartitionService;
56 56 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
57 57 import org.thingsboard.server.queue.util.TbCoreComponent;
58   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  58 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
59 59 import org.thingsboard.server.service.queue.TbClusterService;
60 60 import org.thingsboard.server.service.telemetry.InternalTelemetryService;
61 61
... ...
... ... @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
55 55 import org.thingsboard.server.queue.util.TbCoreComponent;
56 56 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
57 57 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
58   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  58 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
59 59 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
60 60 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
61 61 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
... ...
... ... @@ -45,7 +45,7 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
45 45 import org.thingsboard.server.queue.util.TbRuleEngineComponent;
46 46 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
47 47 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
48   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  48 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
49 49 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
50 50 import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision;
51 51 import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult;
... ...
... ... @@ -21,11 +21,10 @@ import org.springframework.stereotype.Service;
21 21 import org.thingsboard.server.common.data.Tenant;
22 22 import org.thingsboard.server.common.data.TenantProfile;
23 23 import org.thingsboard.server.common.data.id.TenantId;
24   -import org.thingsboard.server.dao.tenant.TenantProfileService;
25 24 import org.thingsboard.server.dao.tenant.TenantService;
26 25 import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
27 26 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
28   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  27 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
29 28
30 29 @Slf4j
31 30 @Service
... ...
... ... @@ -38,7 +38,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
38 38 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
39 39 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
40 40 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
41   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  41 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
42 42 import org.thingsboard.server.service.queue.TbPackCallback;
43 43 import org.thingsboard.server.service.queue.TbPackProcessingContext;
44 44
... ...
... ... @@ -71,7 +71,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
71 71 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
72 72 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
73 73 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
74   -import org.thingsboard.server.service.profile.TbTenantProfileCache;
  74 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
75 75 import org.thingsboard.server.service.queue.TbClusterService;
76 76 import org.thingsboard.server.service.state.DeviceStateService;
77 77
... ...
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java renamed from application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.service.profile;
  16 +package org.thingsboard.server.dao.tenant;
17 17
18 18 import org.thingsboard.server.common.data.TenantProfile;
19 19 import org.thingsboard.server.common.data.id.TenantId;
... ...
... ... @@ -87,8 +87,9 @@ public class TbAwsSqsProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
87 87 sendMsgRequest.withQueueUrl(getQueueUrl(tpi.getFullTopicName()));
88 88 sendMsgRequest.withMessageBody(gson.toJson(new DefaultTbQueueMsg(msg)));
89 89
90   - sendMsgRequest.withMessageGroupId(tpi.getTopic());
91   - sendMsgRequest.withMessageDeduplicationId(UUID.randomUUID().toString());
  90 + String sqsMsgId = UUID.randomUUID().toString();
  91 + sendMsgRequest.withMessageGroupId(sqsMsgId);
  92 + sendMsgRequest.withMessageDeduplicationId(sqsMsgId);
92 93
93 94 ListenableFuture<SendMessageResult> future = producerExecutor.submit(() -> sqsClient.sendMessage(sendMsgRequest));
94 95
... ...
... ... @@ -122,7 +122,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
122 122 log.trace("[{}] Processing msg: {}", sessionId, msg);
123 123 try {
124 124 if (msg instanceof MqttMessage) {
125   - processMqttMsg(ctx, (MqttMessage) msg);
  125 + MqttMessage message = (MqttMessage) msg;
  126 + if (message.decoderResult().isSuccess()) {
  127 + processMqttMsg(ctx, message);
  128 + } else {
  129 + log.error("[{}] Message processing failed: {}", sessionId, message.decoderResult().cause().getMessage());
  130 + ctx.close();
  131 + }
126 132 } else {
127 133 ctx.close();
128 134 }
... ...
... ... @@ -166,4 +166,6 @@ public interface AssetDao extends Dao<Asset> {
166 166 */
167 167 ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId);
168 168
  169 + Long countAssetsByTenantId(TenantId tenantId);
  170 +
169 171 }
... ...
... ... @@ -26,6 +26,7 @@ import org.springframework.cache.Cache;
26 26 import org.springframework.cache.CacheManager;
27 27 import org.springframework.cache.annotation.CacheEvict;
28 28 import org.springframework.cache.annotation.Cacheable;
  29 +import org.springframework.context.annotation.Lazy;
29 30 import org.springframework.stereotype.Service;
30 31 import org.springframework.util.StringUtils;
31 32 import org.thingsboard.server.common.data.Customer;
... ... @@ -44,12 +45,14 @@ import org.thingsboard.server.common.data.page.PageData;
44 45 import org.thingsboard.server.common.data.page.PageLink;
45 46 import org.thingsboard.server.common.data.relation.EntityRelation;
46 47 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  48 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
47 49 import org.thingsboard.server.dao.customer.CustomerDao;
48 50 import org.thingsboard.server.dao.entity.AbstractEntityService;
49 51 import org.thingsboard.server.dao.entityview.EntityViewService;
50 52 import org.thingsboard.server.dao.exception.DataValidationException;
51 53 import org.thingsboard.server.dao.service.DataValidator;
52 54 import org.thingsboard.server.dao.service.PaginatedRemover;
  55 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
53 56 import org.thingsboard.server.dao.tenant.TenantDao;
54 57
55 58 import java.util.ArrayList;
... ... @@ -90,6 +93,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
90 93 @Autowired
91 94 private CacheManager cacheManager;
92 95
  96 + @Autowired
  97 + @Lazy
  98 + private TbTenantProfileCache tenantProfileCache;
  99 +
93 100 @Override
94 101 public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) {
95 102 log.trace("Executing findAssetInfoById [{}]", assetId);
... ... @@ -320,6 +327,15 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
320 327
321 328 @Override
322 329 protected void validateCreate(TenantId tenantId, Asset asset) {
  330 + DefaultTenantProfileConfiguration profileConfiguration =
  331 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  332 + long maxAssets = profileConfiguration.getMaxAssets();
  333 + if (maxAssets > 0) {
  334 + long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId);
  335 + if (maxAssets >= currentAssetsCount) {
  336 + throw new DataValidationException("Can't create assets more then " + maxAssets);
  337 + }
  338 + }
323 339 }
324 340
325 341 @Override
... ...
... ... @@ -203,6 +203,8 @@ public interface DeviceDao extends Dao<Device> {
203 203 */
204 204 ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
205 205
  206 + Long countDevicesByTenantId(TenantId tenantId);
  207 +
206 208 Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId);
207 209
208 210 /**
... ...
... ... @@ -27,6 +27,7 @@ import org.springframework.cache.Cache;
27 27 import org.springframework.cache.CacheManager;
28 28 import org.springframework.cache.annotation.CacheEvict;
29 29 import org.springframework.cache.annotation.Cacheable;
  30 +import org.springframework.context.annotation.Lazy;
30 31 import org.springframework.stereotype.Service;
31 32 import org.springframework.transaction.annotation.Transactional;
32 33 import org.springframework.util.CollectionUtils;
... ... @@ -57,6 +58,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
57 58 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
58 59 import org.thingsboard.server.common.data.security.DeviceCredentials;
59 60 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  61 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
60 62 import org.thingsboard.server.dao.customer.CustomerDao;
61 63 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
62 64 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
... ... @@ -67,6 +69,7 @@ import org.thingsboard.server.dao.event.EventService;
67 69 import org.thingsboard.server.dao.exception.DataValidationException;
68 70 import org.thingsboard.server.dao.service.DataValidator;
69 71 import org.thingsboard.server.dao.service.PaginatedRemover;
  72 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
70 73 import org.thingsboard.server.dao.tenant.TenantDao;
71 74 import org.thingsboard.server.dao.util.mapping.JacksonUtil;
72 75
... ... @@ -120,6 +123,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
120 123 @Autowired
121 124 private EventService eventService;
122 125
  126 + @Autowired
  127 + @Lazy
  128 + private TbTenantProfileCache tenantProfileCache;
  129 +
123 130 @Override
124 131 public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
125 132 log.trace("Executing findDeviceInfoById [{}]", deviceId);
... ... @@ -520,6 +527,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
520 527
521 528 @Override
522 529 protected void validateCreate(TenantId tenantId, Device device) {
  530 + DefaultTenantProfileConfiguration profileConfiguration =
  531 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  532 + long maxDevices = profileConfiguration.getMaxDevices();
  533 + if (maxDevices > 0) {
  534 + long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId);
  535 + if (maxDevices >= currentDevicesCount) {
  536 + throw new DataValidationException("Can't create devices more then " + maxDevices);
  537 + }
  538 + }
523 539 }
524 540
525 541 @Override
... ... @@ -532,7 +548,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
532 548
533 549 @Override
534 550 protected void validateDataImpl(TenantId tenantId, Device device) {
535   - if (StringUtils.isEmpty(device.getName())) {
  551 + if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) {
536 552 throw new DataValidationException("Device name should be specified!");
537 553 }
538 554 if (device.getTenantId() == null) {
... ...
... ... @@ -122,4 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity,
122 122 @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
123 123 List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId);
124 124
  125 + Long countByTenantId(UUID tenantId);
125 126 }
... ...
... ... @@ -176,4 +176,10 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
176 176 }
177 177 return list;
178 178 }
  179 +
  180 + @Override
  181 + public Long countAssetsByTenantId(TenantId tenantId) {
  182 + return assetRepository.countByTenantId(tenantId.getId());
  183 +
  184 + }
179 185 }
... ...
... ... @@ -168,4 +168,6 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
168 168 DeviceEntity findByTenantIdAndId(UUID tenantId, UUID id);
169 169
170 170 Long countByDeviceProfileId(UUID deviceProfileId);
  171 +
  172 + Long countByTenantId(UUID tenantId);
171 173 }
... ...
... ... @@ -219,6 +219,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
219 219 return deviceRepository.countByDeviceProfileId(deviceProfileId);
220 220 }
221 221
  222 + @Override
  223 + public Long countDevicesByTenantId(TenantId tenantId) {
  224 + return deviceRepository.countByTenantId(tenantId.getId());
  225 + }
  226 +
222 227 private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
223 228 List<EntitySubtype> list = Collections.emptyList();
224 229 if (types != null && !types.isEmpty()) {
... ...
dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java renamed from application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java
... ... @@ -13,20 +13,15 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.service.profile;
  16 +package org.thingsboard.server.dao.tenant;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.stereotype.Service;
20   -import org.thingsboard.server.common.data.Device;
21   -import org.thingsboard.server.common.data.DeviceProfile;
22 20 import org.thingsboard.server.common.data.Tenant;
23 21 import org.thingsboard.server.common.data.TenantProfile;
24   -import org.thingsboard.server.common.data.id.DeviceId;
25   -import org.thingsboard.server.common.data.id.DeviceProfileId;
26 22 import org.thingsboard.server.common.data.id.TenantId;
27 23 import org.thingsboard.server.common.data.id.TenantProfileId;
28   -import org.thingsboard.server.dao.device.DeviceProfileService;
29   -import org.thingsboard.server.dao.device.DeviceService;
  24 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
30 25 import org.thingsboard.server.dao.tenant.TenantProfileService;
31 26 import org.thingsboard.server.dao.tenant.TenantService;
32 27
... ...
1 1 TB_QUEUE_TYPE=kafka
2 2 TB_KAFKA_SERVERS=kafka:9092
3   -TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100
... ...
... ... @@ -52,11 +52,13 @@ function AwsSqsProducer() {
52 52 queueUrls.set(responseTopic, responseQueueUrl);
53 53 }
54 54
  55 + let msgId = uuid();
  56 +
55 57 let params = {
56 58 MessageBody: msgBody,
57 59 QueueUrl: responseQueueUrl,
58   - MessageGroupId: 'js_eval',
59   - MessageDeduplicationId: uuid()
  60 + MessageGroupId: msgId,
  61 + MessageDeduplicationId: msgId
60 62 };
61 63
62 64 return new Promise((resolve, reject) => {
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.rule.engine.profile;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.node.ObjectNode;
19 20 import lombok.Data;
20 21 import lombok.extern.slf4j.Slf4j;
21 22 import org.thingsboard.rule.engine.action.TbAlarmResult;
... ... @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
29 30 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
30 31 import org.thingsboard.server.common.data.id.EntityId;
31 32 import org.thingsboard.server.common.data.query.EntityKeyType;
  33 +import org.thingsboard.server.common.data.query.KeyFilter;
32 34 import org.thingsboard.server.common.msg.TbMsg;
33 35 import org.thingsboard.server.common.msg.TbMsgMetaData;
34 36 import org.thingsboard.server.common.msg.queue.ServiceQueue;
... ... @@ -53,6 +55,7 @@ class AlarmState {
53 55 private volatile boolean initialFetchDone;
54 56 private volatile TbMsgMetaData lastMsgMetaData;
55 57 private volatile String lastMsgQueueName;
  58 + private volatile DataSnapshot dataSnapshot;
56 59
57 60 AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) {
58 61 this.deviceProfile = deviceProfile;
... ... @@ -74,7 +77,7 @@ class AlarmState {
74 77
75 78 public <T> boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, AlarmEvalResult> evalFunction) {
76 79 boolean stateUpdate = false;
77   - AlarmSeverity resultSeverity = null;
  80 + AlarmRuleState resultState = null;
78 81 log.debug("[{}] processing update: {}", alarmDefinition.getId(), data);
79 82 for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
80 83 if (!validateUpdate(update, state)) {
... ... @@ -84,15 +87,15 @@ class AlarmState {
84 87 AlarmEvalResult evalResult = evalFunction.apply(state, data);
85 88 stateUpdate |= state.checkUpdate();
86 89 if (AlarmEvalResult.TRUE.equals(evalResult)) {
87   - resultSeverity = state.getSeverity();
  90 + resultState = state;
88 91 break;
89 92 } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
90 93 state.clear();
91 94 stateUpdate |= state.checkUpdate();
92 95 }
93 96 }
94   - if (resultSeverity != null) {
95   - TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity);
  97 + if (resultState != null) {
  98 + TbAlarmResult result = calculateAlarmResult(ctx, resultState);
96 99 if (result != null) {
97 100 pushMsg(ctx, result);
98 101 }
... ... @@ -187,7 +190,8 @@ class AlarmState {
187 190 }
188 191 }
189 192
190   - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) {
  193 + private <T> TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
  194 + AlarmSeverity severity = ruleState.getSeverity();
191 195 if (currentAlarm != null) {
192 196 // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain).
193 197 // Maybe we should fetch alarm every time?
... ... @@ -213,7 +217,7 @@ class AlarmState {
213 217 currentAlarm.setSeverity(severity);
214 218 currentAlarm.setStartTs(System.currentTimeMillis());
215 219 currentAlarm.setEndTs(currentAlarm.getStartTs());
216   - currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode());
  220 + currentAlarm.setDetails(createDetails(ruleState));
217 221 currentAlarm.setOriginator(originator);
218 222 currentAlarm.setTenantId(ctx.getTenantId());
219 223 currentAlarm.setPropagate(alarmDefinition.isPropagate());
... ... @@ -226,6 +230,44 @@ class AlarmState {
226 230 }
227 231 }
228 232
  233 + private <T> JsonNode createDetails(AlarmRuleState ruleState) {
  234 + ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode();
  235 + String alarmDetails = ruleState.getAlarmRule().getAlarmDetails();
  236 +
  237 + if (alarmDetails != null) {
  238 + for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) {
  239 + EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey());
  240 + alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue));
  241 + }
  242 +
  243 + details.put("data", alarmDetails);
  244 + }
  245 +
  246 + return details;
  247 + }
  248 +
  249 + private static String getValueAsString(EntityKeyValue entityKeyValue) {
  250 + Object result = null;
  251 + switch (entityKeyValue.getDataType()) {
  252 + case STRING:
  253 + result = entityKeyValue.getStrValue();
  254 + break;
  255 + case JSON:
  256 + result = entityKeyValue.getJsonValue();
  257 + break;
  258 + case LONG:
  259 + result = entityKeyValue.getLngValue();
  260 + break;
  261 + case DOUBLE:
  262 + result = entityKeyValue.getDblValue();
  263 + break;
  264 + case BOOLEAN:
  265 + result = entityKeyValue.getBoolValue();
  266 + break;
  267 + }
  268 + return String.valueOf(result);
  269 + }
  270 +
229 271 public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) {
230 272 boolean updated = false;
231 273 if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) {
... ...