Browse Source

Alarm service refactroing

pull/7942/head
Andrii Shvaika 3 years ago
parent
commit
c922bbb989
  1. 12
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  2. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  3. 122
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  4. 10
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  5. 102
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  6. 13
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  7. 83
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java
  8. 21
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  9. 39
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  10. 4
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java
  11. 31
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java
  12. 9
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java
  13. 27
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java
  14. 32
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/CreateOrUpdateActiveAlarmRequest.java
  15. 8
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  16. 17
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  17. 153
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  18. 4
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  19. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  20. 17
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
  21. 33
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  22. 186
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  23. 4
      dao/src/main/resources/sql/schema-entities-idx.sql
  24. 211
      dao/src/main/resources/sql/schema-entities.sql
  25. 7
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  26. 232
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
  27. 41
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
  28. 23
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
  29. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
  30. 20
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

12
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -136,7 +136,9 @@ public class AlarmController extends BaseController {
@ResponseBody
public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException {
alarm.setTenantId(getTenantId());
checkNotNull(alarm.getOriginator());
checkEntity(alarm.getId(), alarm, Resource.ALARM);
checkEntityId(alarm.getOriginator(), Operation.READ);
return tbAlarmService.save(alarm, getCurrentUser());
}
@ -159,11 +161,12 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
public AlarmInfo ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
tbAlarmService.ack(alarm, getCurrentUser()).get();
//TODO: return correct error code if the alarm is not found or already cleared
return tbAlarmService.ack(alarm, getCurrentUser());
}
@ApiOperation(value = "Clear Alarm (clearAlarm)",
@ -173,11 +176,12 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
public AlarmInfo clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
tbAlarmService.clear(alarm, getCurrentUser()).get();
//TODO: return correct error code if the alarm is not found or already cleared
return tbAlarmService.clear(alarm, getCurrentUser());
}
@ApiOperation(value = "Assign/Reassign Alarm (assignAlarm)",

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java

@ -72,7 +72,7 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
break;
case ALARM_ACK_RPC_MESSAGE:
if (existentAlarm != null) {
alarmService.ackAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs());
alarmService.acknowledgeAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs());
}
break;
case ALARM_CLEAR_RPC_MESSAGE:

122
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -24,14 +24,20 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -47,7 +53,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
TenantId tenantId = alarm.getTenantId();
try {
Alarm savedAlarm = checkNotNull(alarmSubscriptionService.createOrUpdateAlarm(alarm));
notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user);
AlarmApiCallResult result;
if (alarm.getId() == null) {
result = alarmSubscriptionService.createAlarm(CreateOrUpdateActiveAlarmRequest.fromAlarm(alarm));
} else {
result = alarmSubscriptionService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm));
}
actionType = result.isCreated() ? ActionType.ADDED : ActionType.UPDATED;
if (result.isModified()) {
notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user);
}
return savedAlarm;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e);
@ -56,59 +71,100 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}
@Override
public ListenableFuture<Void> ack(Alarm alarm, User user) {
long ackTs = System.currentTimeMillis();
ListenableFuture<Boolean> future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
return Futures.transform(future, result -> {
public AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis());
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
if (result.isModified()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was acknowledged by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString()))
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())
.put("subtype", "ACK"))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
alarm.setAckTs(ackTs);
alarm.setAcknowledged(true);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user);
return null;
}, MoreExecutors.directExecutor());
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ACK, user);
} else {
throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return result.getAlarm();
}
@Override
public ListenableFuture<Void> clear(Alarm alarm, User user) {
long clearTs = System.currentTimeMillis();
ListenableFuture<Boolean> future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs);
return Futures.transform(future, result -> {
public AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis(), null);
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
if (result.isCleared()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was cleared by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString()))
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())
.put("subtype", "CLEAR"))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
alarm.setClearTs(clearTs);
alarm.setCleared(true);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user);
return null;
}, MoreExecutors.directExecutor());
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_CLEAR, user);
} else {
throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return result.getAlarm();
}
@Override
public Alarm assign(Alarm alarm, User user, UserId assigneeId) {
long assignTs = System.currentTimeMillis();
AlarmOperationResult operationResult = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, assignTs);
notificationEntityService.notifyCreateOrUpdateAlarm(operationResult.getAlarm(), ActionType.ALARM_ASSIGN, user);
return operationResult.getAlarm();
public AlarmInfo assign(Alarm alarm, User user, UserId assigneeId) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, System.currentTimeMillis());
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
AlarmInfo alarmInfo = result.getAlarm();
if (result.isModified()) {
AlarmAssignee assignee = alarmInfo.getAssignee();
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was assigned by user %s to user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName(),
(assignee.getFirstName() == null || assignee.getLastName() == null) ? assignee.getEmail() : assignee.getFirstName() + " " + assignee.getLastName()))
.put("userId", user.getId().toString())
.put("assigneeId", assignee.getId().toString())
.put("subtype", "ASSIGN"))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGN, user);
} else {
throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return alarmInfo;
}
@Override
public Alarm unassign(Alarm alarm, User user) {
long assignTs = System.currentTimeMillis();
AlarmOperationResult operationResult = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs);
notificationEntityService.notifyCreateOrUpdateAlarm(operationResult.getAlarm(), ActionType.ALARM_UNASSIGN, user);
return operationResult.getAlarm();
public AlarmInfo unassign(Alarm alarm, User user) throws ThingsboardException {
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis());
if (!result.isSuccessful()) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
AlarmInfo alarmInfo = result.getAlarm();
if (result.isModified()) {
AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was unassigned by user %s",
(user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())
.put("subtype", "ASSIGN"))
.build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGN, user);
} else {
throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return alarmInfo;
}
@Override

10
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java

@ -15,9 +15,9 @@
*/
package org.thingsboard.server.service.entitiy.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
@ -25,13 +25,13 @@ public interface TbAlarmService {
Alarm save(Alarm entity, User user) throws ThingsboardException;
ListenableFuture<Void> ack(Alarm alarm, User user);
AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException;
ListenableFuture<Void> clear(Alarm alarm, User user);
AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException;
Alarm assign(Alarm alarm, User user, UserId assigneeId);
AlarmInfo assign(Alarm alarm, User user, UserId assigneeId) throws ThingsboardException;
Alarm unassign(Alarm alarm, User user);
AlarmInfo unassign(Alarm alarm, User user) throws ThingsboardException;
Boolean delete(Alarm alarm, User user);
}

102
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -33,6 +33,8 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@ -45,6 +47,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
@ -93,6 +96,37 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return "alarm";
}
@Override
public AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request) {
boolean creationEnabled = apiUsageStateService.getApiUsageState(request.getTenantId()).isAlarmCreationEnabled();
return withWsCallback(alarmService.createAlarm(request, creationEnabled));
}
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
return withWsCallback(alarmService.updateAlarm(request));
}
@Override
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
return withWsCallback(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs));
}
@Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) {
return withWsCallback(alarmService.clearAlarm(tenantId, alarmId, clearTs, details));
}
@Override
public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) {
return withWsCallback(alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs));
}
@Override
public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) {
return withWsCallback(alarmService.unassignAlarm(tenantId, alarmId, assignTs));
}
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
@ -123,44 +157,22 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
@Override
public ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
ListenableFuture<AlarmOperationResult> result = alarmService.ackAlarm(tenantId, alarmId, ackTs);
ListenableFuture<AlarmApiCallResult> result = Futures.immediateFuture(alarmService.acknowledgeAlarm(tenantId, alarmId, ackTs));
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor);
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor);
return Futures.transform(result, AlarmApiCallResult::isSuccessful, wsCallBackExecutor);
}
@Override
public ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
ListenableFuture<AlarmOperationResult> result = clearAlarmForResult(tenantId, alarmId, details, clearTs);
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor);
AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
return Futures.transform(Futures.immediateFuture(result), AlarmApiCallResult::isSuccessful, wsCallBackExecutor);
}
@Override
public ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
ListenableFuture<AlarmOperationResult> result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs);
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor);
return result;
}
@Override
public AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs) {
AlarmOperationResult result = alarmService.assignAlarm(tenantId, alarmId, assigneeId, assignTs);
if (result.isSuccessful()) {
onAlarmUpdated(result);
} else {
log.warn("[{}][{}] Failed to assign alarm.", tenantId, alarmId);
}
return result;
}
@Override
public AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs) {
AlarmOperationResult result = alarmService.unassignAlarm(tenantId, alarmId, assignTs);
if (result.isSuccessful()) {
onAlarmUpdated(result);
} else {
log.warn("[{}][{}] Failed to unassign alarm.", tenantId, alarmId);
}
return result;
AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
return Futures.immediateFuture(new AlarmOperationResult(result));
}
@Override
@ -203,7 +215,28 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return alarmService.findLatestByOriginatorAndType(tenantId, originator, type);
}
@Deprecated
private void onAlarmUpdated(AlarmOperationResult result) {
wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm();
TenantId tenantId = alarm.getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, null, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, null, alarm);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}
});
}
private void onAlarmUpdated(AlarmApiCallResult result) {
wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm();
TenantId tenantId = alarm.getTenantId();
@ -243,9 +276,9 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
private class AlarmUpdateCallback implements FutureCallback<AlarmOperationResult> {
private class AlarmUpdateCallback implements FutureCallback<AlarmApiCallResult> {
@Override
public void onSuccess(@Nullable AlarmOperationResult result) {
public void onSuccess(@Nullable AlarmApiCallResult result) {
onAlarmUpdated(result);
}
@ -255,4 +288,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
}
}
private AlarmApiCallResult withWsCallback(AlarmApiCallResult result) {
if (result.isSuccessful() && result.isModified()) {
Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
}
return result;
}
}

13
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -29,8 +29,10 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
@ -83,15 +85,18 @@ public class DefaultTbAlarmServiceTest {
@Test
public void testSave() throws ThingsboardException {
var alarm = new AlarmInfo();
when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm);
when(alarmSubscriptionService.createAlarm(any())).thenReturn(AlarmApiCallResult.builder()
.successful(true)
.alarm(alarm)
.build());
service.save(alarm, new User());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).createOrUpdateAlarm(eq(alarm));
verify(alarmSubscriptionService, times(1)).createAlarm(any());
}
@Test
public void testAck() {
public void testAck() throws ThingsboardException {
var alarm = new Alarm();
when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
service.ack(alarm, new User(new UserId(UUID.randomUUID())));
@ -102,7 +107,7 @@ public class DefaultTbAlarmServiceTest {
}
@Test
public void testClear() {
public void testClear() throws ThingsboardException {
var alarm = new Alarm();
alarm.setAcknowledged(true);
when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));

83
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java

@ -0,0 +1,83 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.alarm;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.List;
@Data
public class AlarmApiCallResult {
private final boolean successful;
private final boolean created;
private final boolean modified;
private final boolean cleared;
private final AlarmInfo alarm;
private final Alarm old;
private final List<EntityId> propagatedEntitiesList;
@Builder
private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, AlarmInfo alarm, Alarm old, List<EntityId> propagatedEntitiesList) {
this.successful = successful;
this.created = created;
this.modified = modified;
this.cleared = cleared;
this.alarm = alarm;
this.old = old;
this.propagatedEntitiesList = propagatedEntitiesList;
}
public AlarmApiCallResult(AlarmApiCallResult other, List<EntityId> propagatedEntitiesList) {
this.successful = other.successful;
this.created = other.created;
this.modified = other.modified;
this.cleared = other.cleared;
this.alarm = other.alarm;
this.old = other.old;
this.propagatedEntitiesList = propagatedEntitiesList;
}
boolean hasSeverityChange() {
if (alarm == null || old == null) {
return false;
} else {
return !alarm.getSeverity().equals(old.getSeverity());
}
}
public AlarmSeverity getOldSeverity() {
return hasSeverityChange() ? old.getSeverity() : null;
}
public boolean isPropagationChanged() {
if (alarm == null || old == null) {
return false;
}
return (alarm.isPropagate() != old.isPropagate()) ||
(alarm.isPropagateToOwner() != old.isPropagateToOwner()) ||
(alarm.isPropagateToTenant() != old.isPropagateToTenant()) ||
(!alarm.getPropagateRelationTypes().equals(old.getPropagateRelationTypes()));
}
}

21
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.alarm;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
@ -25,8 +26,10 @@ import org.thingsboard.server.common.data.id.EntityId;
import java.util.Collections;
import java.util.List;
@Builder
@Data
@AllArgsConstructor
@Deprecated
public class AlarmOperationResult {
private final Alarm alarm;
private final boolean successful;
@ -34,18 +37,12 @@ public class AlarmOperationResult {
private final AlarmSeverity oldSeverity;
private final List<EntityId> propagatedEntitiesList;
private final AlarmAssigneeUpdate assigneeUpdate;
public AlarmOperationResult(Alarm alarm, AlarmAssigneeUpdate assigneeUpdate, List<EntityId> propagatedEntitiesList) {
this(alarm, true, false, null, propagatedEntitiesList, assigneeUpdate);
}
public AlarmOperationResult(Alarm alarm, boolean successful) {
this(alarm, successful, Collections.emptyList());
}
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarm, successful, false, null, propagatedEntitiesList, null);
this(alarm, successful, false, null, propagatedEntitiesList);
}
public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
@ -54,6 +51,14 @@ public class AlarmOperationResult {
this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList;
this.oldSeverity = null;
this.assigneeUpdate = null;
}
//Temporary while we have not removed the AlarmOperationResult.
public AlarmOperationResult(AlarmApiCallResult result) {
this.alarm = result.getAlarm() != null ? new Alarm(result.getAlarm()) : null;
this.successful = result.isSuccessful() && (result.isCreated() || result.isModified());
this.created = result.isCreated();
this.oldSeverity = result.getOldSeverity();
this.propagatedEntitiesList = result.getPropagatedEntitiesList();
}
}

39
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -40,43 +40,56 @@ import java.util.Collection;
public interface AlarmService extends EntityDaoService {
// New API, since 3.5.
/*
* New API, since 3.5.
*/
/**
* Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request);
AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request);
/**
* Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled);
AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled);
/**
* Designed to update existing alarm. Accepts only part of the alarm fields.
*
*/
AlarmOperationResult updateAlarm(AlarmUpdateRequest request);
AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
// Legacy API, before 3.5
AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
AlarmOperationResult createOrUpdateAlarm(Alarm alarm);
AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled);
AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long ts);
// Other API
AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long ts);
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId);
/*
* Legacy API, before 3.5.
*/
@Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult createOrUpdateAlarm(Alarm alarm);
@Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled);
@Deprecated(since = "3.5.0", forRemoval = true)
ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
@Deprecated(since = "3.5.0", forRemoval = true)
ListenableFuture<AlarmOperationResult> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long ts);
AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long ts);
@Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
// Other API
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);

4
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmAssignee.java

@ -15,11 +15,15 @@
*/
package org.thingsboard.server.common.data.alarm;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.UserId;
import java.io.Serializable;
@Builder
@AllArgsConstructor
@Data
public class AlarmAssignee implements Serializable {

31
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmModificationRequest.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.alarm;
import org.thingsboard.server.common.data.id.TenantId;
public interface AlarmModificationRequest {
TenantId getTenantId();
long getStartTs();
long getEndTs();
void setStartTs(long startTs);
void setEndTs(long endTs);
}

9
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java

@ -16,19 +16,28 @@
package org.thingsboard.server.common.data.alarm;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Collections;
import java.util.List;
@Builder
@Data
public class AlarmPropagationInfo {
public static AlarmPropagationInfo EMPTY = new AlarmPropagationInfo(false, false, false, Collections.emptyList());
@ApiModelProperty(position = 1, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate;
@ApiModelProperty(position = 2, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
private boolean propagateToOwner;
@ApiModelProperty(position = 3, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant;
@NoXss
@ApiModelProperty(position = 4, value = "JSON array of relation types that should be used for propagation. " +
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")

27
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java

@ -17,29 +17,54 @@ package org.thingsboard.server.common.data.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
public class AlarmUpdateRequest {
@Builder
public class AlarmUpdateRequest implements AlarmModificationRequest {
@NotNull
@ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId;
@NotNull
@ApiModelProperty(position = 2, value = "JSON object with the alarm Id. " +
"Specify this field to update the alarm. " +
"Referencing non-existing alarm Id will cause error. " +
"Omit this field to create new alarm.")
private AlarmId alarmId;
@NotNull
@ApiModelProperty(position = 3, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity;
@ApiModelProperty(position = 4, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs;
@ApiModelProperty(position = 5, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@NoXss
@ApiModelProperty(position = 6, value = "JSON object with alarm details")
private JsonNode details;
@Valid
@ApiModelProperty(position = 7, value = "JSON object with propagation details")
private AlarmPropagationInfo propagation;
public static AlarmUpdateRequest fromAlarm(Alarm a) {
return AlarmUpdateRequest.builder()
.tenantId(a.getTenantId())
.severity((a.getSeverity()))
.startTs(a.getStartTs())
.endTs(a.getEndTs())
.details(a.getDetails())
.propagation(AlarmPropagationInfo.builder()
.propagate(a.isPropagate())
.propagateToOwner(a.isPropagateToOwner())
.propagateToTenant(a.isPropagateToTenant())
.propagateRelationTypes(a.getPropagateRelationTypes()).build())
.build();
}
}

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

@ -17,34 +17,64 @@ package org.thingsboard.server.common.data.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
public class CreateOrUpdateActiveAlarmRequest {
@Builder
public class CreateOrUpdateActiveAlarmRequest implements AlarmModificationRequest {
@NotNull
@ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId;
@ApiModelProperty(position = 2, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId;
@NotNull
@ApiModelProperty(position = 3, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@Length(fieldName = "type")
private String type;
@NotNull
@ApiModelProperty(position = 4, required = true, value = "JSON object with alarm originator id")
private EntityId originator;
@NotNull
@ApiModelProperty(position = 5, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity;
@ApiModelProperty(position = 6, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs;
@ApiModelProperty(position = 7, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@NoXss
@ApiModelProperty(position = 8, value = "JSON object with alarm details")
private JsonNode details;
@Valid
@ApiModelProperty(position = 9, value = "JSON object with propagation details")
private AlarmPropagationInfo propagation;
public static CreateOrUpdateActiveAlarmRequest fromAlarm(Alarm a) {
return CreateOrUpdateActiveAlarmRequest.builder()
.tenantId(a.getTenantId())
.customerId(a.getCustomerId())
.type(a.getType())
.originator(a.getOriginator())
.severity((a.getSeverity()))
.startTs(a.getStartTs())
.endTs(a.getEndTs())
.details(a.getDetails())
.propagation(AlarmPropagationInfo.builder()
.propagate(a.isPropagate())
.propagateToOwner(a.isPropagateToOwner())
.propagateToTenant(a.isPropagateToTenant())
.propagateRelationTypes(a.getPropagateRelationTypes()).build())
.build();
}
}

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

@ -25,6 +25,14 @@ import java.util.UUID;
*/
public class EntityIdFactory {
public static EntityId getByTypeAndUuid(int type, String uuid) {
return getByTypeAndUuid(EntityType.values()[type], UUID.fromString(uuid));
}
public static EntityId getByTypeAndUuid(String type, String uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), UUID.fromString(uuid));
}
public static EntityId getByTypeAndId(String type, String uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), UUID.fromString(uuid));
}

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

@ -15,13 +15,15 @@
*/
package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
@ -72,5 +74,16 @@ public interface AlarmDao extends Dao<Alarm> {
void deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId);
AlarmInfo acknowledgeAlarm(TenantId tenantId, AlarmId id);
AlarmApiCallResult createOrUpdateActiveAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled);
AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId id, long ackTs);
AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime);
AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime);
}

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

@ -20,17 +20,16 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmModificationRequest;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.NameLabelAndCustomerDetails;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -57,8 +55,10 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -77,34 +77,52 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("AlarmDaoService")
@Slf4j
@RequiredArgsConstructor
public class BaseAlarmService extends AbstractEntityService implements AlarmService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
@Autowired
private AlarmDao alarmDao;
private final TenantService tenantService;
private final AlarmDao alarmDao;
private final EntityService entityService;
private final DataValidator<Alarm> alarmDataValidator;
@Autowired
private EntityService entityService;
@Autowired
private DataValidator<Alarm> alarmDataValidator;
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
validateAlarmRequest(request);
return withPropagated(alarmDao.updateAlarm(request));
}
@Override
public AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request) {
return createAlarm(request, true);
}
@Override
public AlarmOperationResult updateAlarm(AlarmUpdateRequest request) {
return null;
public AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) {
validateAlarmRequest(request);
CustomerId customerId = entityService.fetchEntityCustomerId(request.getTenantId(), request.getOriginator()).orElse(null);
if (customerId == null && request.getCustomerId() != null) {
throw new DataValidationException("Can't assign alarm to customer. Originator is not assigned to customer!");
} else if (customerId != null && request.getCustomerId() != null && !customerId.equals(request.getCustomerId())) {
throw new DataValidationException("Can't assign alarm to customer. Originator belongs to different customer!");
}
request.setCustomerId(customerId);
AlarmApiCallResult result = alarmDao.createOrUpdateActiveAlarm(request, alarmCreationEnabled);
if (!result.isSuccessful() && !alarmCreationEnabled) {
throw new ApiUsageLimitsExceededException("Alarms creation is disabled");
}
return withPropagated(result);
}
@Override
public AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request) {
return null;
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
return withPropagated(alarmDao.acknowledgeAlarm(tenantId, alarmId, ackTs));
}
@Override
public AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) {
return null;
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) {
return withPropagated(alarmDao.clearAlarm(tenantId, alarmId, clearTs, details));
}
@Override
@ -154,6 +172,20 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmDao.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
@Override
@Transactional
public AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId) {
log.debug("Deleting Alarm Id: {}", alarmId);
AlarmInfo alarm = alarmDao.findAlarmInfoById(tenantId, alarmId.getId());
if (alarm == null) {
return AlarmApiCallResult.builder().successful(false).build();
} else {
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId());
return AlarmApiCallResult.builder().alarm(alarm).successful(true).build();
}
}
@Override
@Transactional
public AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId) {
@ -175,7 +207,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
}
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws ExecutionException, InterruptedException {
Set<EntityId> propagatedEntitiesSet = new LinkedHashSet<>();
propagatedEntitiesSet.add(alarm.getOriginator());
if (alarm.isPropagate()) {
@ -228,7 +260,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else {
propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result));
}
return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList, null);
return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList);
}
@Override
@ -261,43 +293,13 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
@Override
public AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) {
return getAndUpdate(tenantId, alarmId, new Function<>() {
@Nullable
@Override
public AlarmOperationResult apply(@Nullable Alarm alarm) {
if (alarm == null || assigneeId.equals(alarm.getAssigneeId())) {
return new AlarmOperationResult(alarm, false);
} else {
alarm.setAssigneeId(assigneeId);
alarm.setAssignTs(assignTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
AlarmInfo alarmInfo = alarmDao.findAlarmInfoById(tenantId, alarm.getUuidId());
return new AlarmOperationResult(alarm,
new AlarmAssigneeUpdate(false, alarmInfo.getAssignee()),
new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) {
return withPropagated(alarmDao.assignAlarm(tenantId, alarmId, assigneeId, assignTime));
}
@Override
public AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTime) {
return getAndUpdate(tenantId, alarmId, new Function<>() {
@Nullable
@Override
public AlarmOperationResult apply(@Nullable Alarm alarm) {
if (alarm == null || alarm.getAssigneeId() == null) {
return new AlarmOperationResult(alarm, false);
} else {
alarm.setAssigneeId(null);
alarm.setAssignTs(assignTime);
alarm = alarmDao.save(alarm.getTenantId(), alarm);
return new AlarmOperationResult(alarm, new AlarmAssigneeUpdate(true, null),
new ArrayList<>(getPropagationEntityIds(alarm)));
}
}
});
public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime) {
return withPropagated(alarmDao.unassignAlarm(tenantId, alarmId, unassignTime));
}
@Override
@ -340,7 +342,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else if (alarmStatus != null) {
asf = AlarmStatusFilter.from(alarmStatus);
} else {
asf= AlarmStatusFilter.empty();
asf = AlarmStatusFilter.empty();
}
Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, asf, assigneeId);
@ -391,6 +393,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return existing;
}
private List<EntityId> getPropagationEntityIdsList(Alarm alarm) {
return new ArrayList<>(getPropagationEntityIds(alarm));
}
private Set<EntityId> getPropagationEntityIds(Alarm alarm) {
if (alarm.isPropagate() || alarm.isPropagateToOwner() || alarm.isPropagateToTenant()) {
List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId());
@ -425,4 +431,39 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return EntityType.ALARM;
}
//TODO: refactor to use efficient caching.
private AlarmApiCallResult withPropagated(AlarmApiCallResult result) {
if (result.isSuccessful() && result.getAlarm() != null) {
List<EntityId> propagationEntities;
if (result.isPropagationChanged()) {
try {
propagationEntities = createEntityAlarmRecords(result.getAlarm());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
} else {
propagationEntities = getPropagationEntityIdsList(result.getAlarm());
}
return new AlarmApiCallResult(result, propagationEntities);
} else {
return result;
}
}
private void validateAlarmRequest(AlarmModificationRequest request) {
ConstraintValidator.validateFields(request);
if (request.getStartTs() > request.getEndTs()) {
throw new DataValidationException("Alarm start ts can't be greater then alarm end ts!");
}
if (!tenantService.tenantExists(request.getTenantId())) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
if (request.getStartTs() == 0L) {
request.setStartTs(System.currentTimeMillis());
}
if (request.getEndTs() == 0L) {
request.setEndTs(request.getStartTs());
}
}
}

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

@ -291,7 +291,7 @@ public class ModelConstants {
public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ALARM_TYPE_PROPERTY = "type";
public static final String ALARM_DETAILS_PROPERTY = "details";
public static final String ALARM_DETAILS_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ALARM_STATUS_PROPERTY = "status";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name";
@ -314,6 +314,8 @@ public class ModelConstants {
public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant";
public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types";
public static final String ALARM_OPERATION_RESULT_PROPERTY = "operation_result";
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
public static final String ALARM_COMMENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -113,7 +113,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
private Long assignTs;
@Type(type = "json")
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
@Column(name = ModelConstants.ALARM_DETAILS_PROPERTY)
private JsonNode details;
@Column(name = ALARM_PROPAGATE_PROPERTY)

17
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java

@ -47,13 +47,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_VIEW_NAME;
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ALARM_VIEW_NAME)
@NamedNativeQueries({
@NamedNativeQuery(
name = "AlarmEntity.acknowledgeAlarm",
query = "SELECT * FROM acknowledge_alarm(:t_id, :a_id, :a_ts)",
resultClass = AlarmInfoEntity.class
)
})
public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
@Column(name = ALARM_ORIGINATOR_NAME_PROPERTY)
@ -73,16 +66,6 @@ public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
super();
}
public AlarmInfoEntity(AlarmEntity alarmEntity,
String assigneeFirstName,
String assigneeLastName,
String assigneeEmail) {
super(alarmEntity);
this.assigneeFirstName = assigneeFirstName;
this.assigneeLastName = assigneeLastName;
this.assigneeEmail = assigneeEmail;
}
@Override
public AlarmInfo toData() {
AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm());

33
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -18,11 +18,11 @@ package org.thingsboard.server.dao.sql.alarm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
@ -179,8 +179,31 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Query(value = "SELECT a FROM AlarmInfoEntity a WHERE a.tenantId = :tenantId AND a.id = :alarmId")
AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId);
// See named native query for definition
AlarmInfoEntity acknowledgeAlarm(@Param("t_id") UUID tenantId,
@Param("a_id") UUID alarmId,
@Param("a_ts") long ts);
@Procedure(procedureName = "create_or_update_active_alarm")
String createOrUpdateActiveAlarm(@Param("t_id") UUID tenantId, @Param("c_id") UUID customerId,
@Param("a_id") UUID alarmId, @Param("a_created_ts") long createdTime,
@Param("a_o_id") UUID originatorId, @Param("a_o_type") int originatorType,
@Param("a_type") String type, @Param("a_severity") String severity,
@Param("a_start_ts") long startTs, @Param("a_end_ts") long endTs, @Param("a_details") String detailsAsString,
@Param("a_propagate") boolean propagate, @Param("a_propagate_to_owner") boolean propagateToOwner,
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes,
@Param("a_creation_enabled") boolean alarmCreationEnabled);
@Procedure(procedureName = "update_alarm")
String updateAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_severity") String severity,
@Param("a_start_ts") long startTs, @Param("a_end_ts") long endTs, @Param("a_details") String detailsAsString,
@Param("a_propagate") boolean propagate, @Param("a_propagate_to_owner") boolean propagateToOwner,
@Param("a_propagate_to_tenant") boolean propagateToTenant, @Param("a_propagation_types") String propagationTypes);
@Procedure(procedureName = "acknowledge_alarm")
String acknowledgeAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts);
@Procedure(procedureName = "clear_alarm")
String clearAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long ts, @Param("a_details") String details);
@Procedure(procedureName = "assign_alarm")
String assignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("u_id") UUID userId, @Param("a_ts") long assignTime);
@Procedure(procedureName = "unassign_alarm")
String unassignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long unassignTime);
}

186
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -15,25 +15,32 @@
*/
package org.thingsboard.server.dao.sql.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.UUIDCharType;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmPropagationInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
@ -41,19 +48,21 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.query.AlarmQueryRepository;
import org.thingsboard.server.dao.util.SqlDao;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ -222,8 +231,171 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
}
@Override
public AlarmInfo acknowledgeAlarm(TenantId tenantId, AlarmId id) {
return DaoUtil.getData(alarmRepository.acknowledgeAlarm(tenantId.getId(), id.getId(), System.currentTimeMillis()));
public AlarmApiCallResult createOrUpdateActiveAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) {
AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation());
return toAlarmApiResult(alarmRepository.createOrUpdateActiveAlarm(
request.getTenantId().getId(),
request.getCustomerId() != null ? request.getCustomerId().getId() : CustomerId.NULL_UUID,
UUID.randomUUID(),
System.currentTimeMillis(),
request.getOriginator().getId(),
request.getOriginator().getEntityType().ordinal(),
request.getType(),
request.getSeverity().name(),
request.getStartTs(), request.getEndTs(),
getDetailsAsString(request.getDetails()),
ap.isPropagate(),
ap.isPropagateToOwner(),
ap.isPropagateToTenant(),
getPropagationTypes(ap),
alarmCreationEnabled
));
}
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation());
return toAlarmApiResult(alarmRepository.updateAlarm(
request.getTenantId().getId(),
request.getAlarmId().getId(),
request.getSeverity().name(),
request.getStartTs(), request.getEndTs(),
getDetailsAsString(request.getDetails()),
ap.isPropagate(),
ap.isPropagateToOwner(),
ap.isPropagateToTenant(),
getPropagationTypes(ap)
));
}
@Override
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId id, long ackTs) {
return toAlarmApiResult(alarmRepository.acknowledgeAlarm(tenantId.getId(), id.getId(), ackTs));
}
@Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) {
return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, getDetailsAsString(details)));
}
@Override
public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId id, UserId assigneeId, long assignTime) {
return toAlarmApiResult(alarmRepository.assignAlarm(tenantId.getId(), id.getId(), assigneeId.getId(), assignTime));
}
@Override
public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId id, long unassignTime) {
return toAlarmApiResult(alarmRepository.unassignAlarm(tenantId.getId(), id.getId(), unassignTime));
}
@NotNull
private static String getPropagationTypes(AlarmPropagationInfo ap) {
String propagateRelationTypes;
if (!CollectionUtils.isEmpty(ap.getPropagateRelationTypes())) {
propagateRelationTypes = String.join(",", ap.getPropagateRelationTypes());
} else {
propagateRelationTypes = "";
}
return propagateRelationTypes;
}
private static AlarmPropagationInfo getSafePropagationInfo(AlarmPropagationInfo ap) {
return ap != null ? ap : AlarmPropagationInfo.EMPTY;
}
private static String getDetailsAsString(JsonNode details) {
var detailsStr = JacksonUtil.toString(details);
if (StringUtils.isEmpty(detailsStr)) {
detailsStr = "{}";
}
return detailsStr;
}
private AlarmApiCallResult toAlarmApiResult(String str) {
var json = JacksonUtil.toJsonNode(str);
var result = AlarmApiCallResult.builder();
boolean success = json.get("success").asBoolean();
result.successful(success);
if (success) {
boolean modified = false;
boolean created = false;
boolean cleared = false;
if (json.has("modified")) {
modified = json.get("modified").asBoolean();
}
if (json.has("created")) {
created = json.get("created").asBoolean();
}
if (json.has("cleared")) {
cleared = json.get("cleared").asBoolean();
}
result.created(created);
result.cleared(cleared);
result.modified(created || cleared || modified);
if (json.has("alarm") && !json.get("alarm").isNull()) {
result.alarm(toAlarmInfo(json.get("alarm")));
}
if (json.has("old") && !json.get("old").isNull()) {
result.old(toAlarm(json.get("old")));
}
}
return result.build();
}
private AlarmInfo toAlarmInfo(JsonNode json) {
AlarmInfo alarmInfo = new AlarmInfo(toAlarm(json));
getSafe(json, ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY).ifPresent(alarmInfo::setOriginatorName);
getSafe(json, ModelConstants.ALARM_ORIGINATOR_LABEL_PROPERTY).ifPresent(alarmInfo::setOriginatorLabel);
if (alarmInfo.getAssigneeId() != null) {
var assigneeBuilder = AlarmAssignee.builder().id(alarmInfo.getAssigneeId());
getSafe(json, ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY).ifPresent(assigneeBuilder::firstName);
getSafe(json, ModelConstants.ALARM_ASSIGNEE_LAST_NAME_PROPERTY).ifPresent(assigneeBuilder::lastName);
getSafe(json, ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY).ifPresent(assigneeBuilder::email);
alarmInfo.setAssignee(assigneeBuilder.build());
}
return alarmInfo;
}
private Alarm toAlarm(JsonNode json) {
Alarm alarm = new Alarm(new AlarmId(UUID.fromString(json.get(ModelConstants.ID_PROPERTY).asText())));
alarm.setCreatedTime(json.get(ModelConstants.CREATED_TIME_PROPERTY).asLong());
getSafe(json, ModelConstants.TENANT_ID_COLUMN).ifPresent(s -> alarm.setTenantId(TenantId.fromUUID(UUID.fromString(s))));
getSafe(json, ModelConstants.CUSTOMER_ID_PROPERTY).ifPresent(s -> alarm.setCustomerId(new CustomerId(UUID.fromString(s))));
getSafe(json, ModelConstants.ASSIGNEE_ID_PROPERTY).ifPresent(s -> alarm.setAssigneeId(new UserId(UUID.fromString(s))));
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(
json.get(ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY).asInt(),
json.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY).asText()));
getSafe(json, ModelConstants.ALARM_TYPE_PROPERTY).ifPresent(alarm::setType);
getSafe(json, ModelConstants.ALARM_SEVERITY_PROPERTY).map(AlarmSeverity::valueOf).ifPresent(alarm::setSeverity);
alarm.setAcknowledged(json.get(ModelConstants.ALARM_ACKNOWLEDGED_PROPERTY).asBoolean());
alarm.setCleared(json.get(ModelConstants.ALARM_CLEARED_PROPERTY).asBoolean());
alarm.setPropagate(json.get(ModelConstants.ALARM_PROPAGATE_PROPERTY).asBoolean());
alarm.setPropagateToOwner(json.get(ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY).asBoolean());
alarm.setPropagateToTenant(json.get(ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY).asBoolean());
alarm.setStartTs(json.get(ModelConstants.ALARM_START_TS_PROPERTY).asLong());
alarm.setEndTs(json.get(ModelConstants.ALARM_END_TS_PROPERTY).asLong());
alarm.setAckTs(json.get(ModelConstants.ALARM_ACK_TS_PROPERTY).asLong());
alarm.setClearTs(json.get(ModelConstants.ALARM_CLEAR_TS_PROPERTY).asLong());
alarm.setAssignTs(json.get(ModelConstants.ALARM_ASSIGN_TS_PROPERTY).asLong());
getSafe(json, ModelConstants.ALARM_DETAILS_PROPERTY).map(JacksonUtil::toJsonNode).ifPresent(alarm::setDetails);
alarm.setPropagateRelationTypes(getSafe(json, ModelConstants.ALARM_PROPAGATE_RELATION_TYPES).filter(StringUtils::isNoneEmpty)
.map(s -> Arrays.asList(s.split(","))).orElse(Collections.emptyList()));
return alarm;
}
private static Optional<String> getSafe(JsonNode json, String fieldName) {
if (json.has(fieldName)) {
var element = json.get(fieldName);
if (element.isNull() || !element.isTextual()) {
return Optional.empty();
} else {
return Optional.of(element.asText());
}
} else {
return Optional.empty();
}
}
@Override

4
dao/src/main/resources/sql/schema-entities-idx.sql

@ -20,8 +20,8 @@ CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time_active
ON alarm USING btree (tenant_id, type, created_time DESC) WHERE cleared = false;
CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active
ON alarm USING btree (originator_id, type, created_time DESC) WHERE cleared = false;
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC);

211
dao/src/main/resources/sql/schema-entities.sql

@ -800,7 +800,7 @@ CREATE TABLE IF NOT EXISTS user_settings (
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
);
DROP VIEW IF EXISTS alarm_info;
DROP VIEW IF EXISTS alarm_info CASCADE;
CREATE VIEW alarm_info AS
SELECT a.*,
(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK'
@ -833,12 +833,211 @@ u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email
FROM alarm a
LEFT JOIN tb_user u ON u.id = a.assignee_id;
CREATE OR REPLACE FUNCTION create_or_update_active_alarm(
t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint,
a_o_id uuid, a_o_type integer, a_type varchar,
a_severity varchar, a_start_ts bigint, a_end_ts bigint,
a_details varchar,
a_propagate boolean, a_propagate_to_owner boolean,
a_propagate_to_tenant boolean, a_propagation_types varchar,
a_creation_enabled boolean)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid;
existing alarm;
result alarm_info;
row_count integer;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type ORDER BY a.start_ts DESC FOR UPDATE;
IF existing.id IS NULL OR existing.cleared IS TRUE THEN
IF a_creation_enabled = FALSE THEN
RETURN json_build_object('success', false)::text;
END IF;
IF c_id = null_id THEN
c_id = NULL;
end if;
INSERT INTO alarm
(tenant_id, customer_id, id, created_time,
originator_id, originator_type, type,
severity, start_ts, end_ts,
additional_info,
propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types,
acknowledged, ack_ts,
cleared, clear_ts,
assignee_id, assign_ts)
VALUES
(t_id, c_id, a_id, a_created_ts,
a_o_id, a_o_type, a_type,
a_severity, a_start_ts, a_end_ts,
a_details,
a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
false, 0, false, 0, NULL, 0);
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
ELSE
UPDATE alarm a
SET severity = a_severity,
start_ts = a_start_ts,
end_ts = a_end_ts,
additional_info = a_details,
propagate = a_propagate,
propagate_to_owner = a_propagate_to_owner,
propagate_to_tenant = a_propagate_to_tenant,
propagate_relation_types = a_propagation_types
WHERE a.id = existing.id
AND a.tenant_id = t_id
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
GET DIAGNOSTICS row_count = ROW_COUNT;
SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id;
IF row_count > 0 THEN
RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
ELSE
RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text;
END IF;
END IF;
END
$$;
DROP FUNCTION IF EXISTS update_alarm;
CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint,
a_details varchar,
a_propagate boolean, a_propagate_to_owner boolean,
a_propagate_to_tenant boolean, a_propagation_types varchar)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
row_count integer;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
UPDATE alarm a
SET severity = a_severity,
start_ts = a_start_ts,
end_ts = a_end_ts,
additional_info = a_details,
propagate = a_propagate,
propagate_to_owner = a_propagate_to_owner,
propagate_to_tenant = a_propagate_to_tenant,
propagate_relation_types = a_propagation_types
WHERE a.id = a_id
AND a.tenant_id = t_id
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
GET DIAGNOSTICS row_count = ROW_COUNT;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
IF row_count > 0 THEN
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
ELSE
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text;
END IF;
END
$$;
DROP FUNCTION IF EXISTS acknowledge_alarm;
CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS alarm_info LANGUAGE plpgsql
AS $$
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id and a.acknowledged = false;
RETURN (SELECT a FROM alarm_info a WHERE a.tenant_id = t_id AND id = a_id);
END;
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF NOT (existing.acknowledged) THEN
modified = TRUE;
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS clear_alarm;
CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
cleared boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF NOT(existing.cleared) THEN
cleared = TRUE;
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS assign_alarm;
CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN
modified = TRUE;
UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;
DROP FUNCTION IF EXISTS unassign_alarm;
CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS varchar
LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
IF existing IS NULL THEN
RETURN json_build_object('success', false)::text;
END IF;
IF existing.assignee_id IS NOT NULL THEN
modified = TRUE;
UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
END
$$;

7
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java

@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Arrays;
@ -188,7 +189,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get();
alarmService.acknowledgeAlarm(tenantId, created.getId(), System.currentTimeMillis());
created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -257,7 +258,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertNotNull(tenantUser);
AlarmOperationResult assignmentResult = alarmService.assignAlarm(tenantId, created.getId(), tenantUser.getId(), ts);
AlarmApiCallResult assignmentResult = alarmService.assignAlarm(tenantId, created.getId(), tenantUser.getId(), ts);
created = assignmentResult.getAlarm();
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -697,7 +698,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
created = alarmService.ackAlarm(tenantId, created.getId(), System.currentTimeMillis()).get().getAlarm();
created = new Alarm(alarmService.acknowledgeAlarm(tenantId, created.getId(), System.currentTimeMillis()).getAlarm());
pageLink.setPage(0);
pageLink.setPageSize(10);

232
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java

@ -17,23 +17,35 @@ package org.thingsboard.server.dao.sql.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.AbstractJpaDaoTest;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmDao;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Created by Valerii Sosliuk on 5/21/2017.
@ -48,35 +60,232 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test
public void testFindLatestByOriginatorAndType() throws ExecutionException, InterruptedException, TimeoutException {
log.info("Current system time in millis = {}", System.currentTimeMillis());
UUID tenantId = UUID.fromString("d4b68f40-3e96-11e7-a884-898080180d6b");
TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID originator2Id = UUID.fromString("d4b68f42-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
UUID alarm2Id = UUID.fromString("d4b68f44-3e96-11e7-a884-898080180d6b");
UUID alarm3Id = UUID.fromString("d4b68f45-3e96-11e7-a884-898080180d6b");
int alarmCountBeforeSave = alarmDao.find(TenantId.fromUUID(tenantId)).size();
saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
// The find method does not filter by tenant. It is just using the tenantId for rate limits if any.
var alarmsBeforeSave = alarmDao.find(tenantId).stream().filter(a -> a.getTenantId().equals(tenantId)).collect(Collectors.toList());
int alarmCountBeforeSave = alarmsBeforeSave.size();
saveAlarm(alarm1Id, tenantId.getId(), originator1Id, "TEST_ALARM");
//The timestamp of the startTime should be different in order for test to always work
Thread.sleep(1);
saveAlarm(alarm2Id, tenantId, originator1Id, "TEST_ALARM");
saveAlarm(alarm3Id, tenantId, originator2Id, "TEST_ALARM");
int alarmCountAfterSave = alarmDao.find(TenantId.fromUUID(tenantId)).size();
assertEquals(3, alarmCountAfterSave - alarmCountBeforeSave);
saveAlarm(alarm2Id, tenantId.getId(), originator1Id, "TEST_ALARM");
saveAlarm(alarm3Id, tenantId.getId(), originator2Id, "TEST_ALARM");
var alarmsAfterSave = alarmDao.find(tenantId).stream().filter(a -> a.getTenantId().equals(tenantId)).collect(Collectors.toList());
int alarmCountAfterSave = alarmsAfterSave.size();
int diff = alarmCountAfterSave - alarmCountBeforeSave;
if (diff != 3) {
System.out.println("test");
}
assertEquals(3, diff);
ListenableFuture<Alarm> future = alarmDao
.findLatestByOriginatorAndTypeAsync(TenantId.fromUUID(tenantId), new DeviceId(originator1Id), "TEST_ALARM");
.findLatestByOriginatorAndTypeAsync(tenantId, new DeviceId(originator1Id), "TEST_ALARM");
Alarm alarm = future.get(30, TimeUnit.SECONDS);
assertNotNull(alarm);
assertEquals(alarm2Id, alarm.getId().getId());
}
@Test
public void createOrUpdateActiveAlarm() {
TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
DeviceId deviceId = new DeviceId(UUID.randomUUID());
CreateOrUpdateActiveAlarmRequest request = CreateOrUpdateActiveAlarmRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.MAJOR)
.build();
AlarmApiCallResult result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
UUID newAlarmId = result.getAlarm().getUuidId();
AlarmInfo afterSave = alarmDao.findAlarmInfoById(tenantId, newAlarmId);
assertEquals(afterSave, result.getAlarm());
request = CreateOrUpdateActiveAlarmRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.CRITICAL)
.build();
result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertFalse(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(newAlarmId, result.getAlarm().getUuidId());
afterSave = alarmDao.findAlarmInfoById(tenantId, newAlarmId);
assertEquals(afterSave, result.getAlarm());
alarmDao.clearAlarm(tenantId, result.getAlarm().getId(), System.currentTimeMillis(), result.getAlarm().getDetails());
request = CreateOrUpdateActiveAlarmRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.CRITICAL)
.build();
result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertNotEquals(newAlarmId, result.getAlarm().getUuidId());
afterSave = alarmDao.findAlarmInfoById(tenantId, result.getAlarm().getUuidId());
assertEquals(afterSave, result.getAlarm());
alarmDao.clearAlarm(tenantId, result.getAlarm().getId(), System.currentTimeMillis(), result.getAlarm().getDetails());
request = CreateOrUpdateActiveAlarmRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE2")
.severity(AlarmSeverity.CRITICAL)
.build();
result = alarmDao.createOrUpdateActiveAlarm(request, true);
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCreated());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertNotEquals(newAlarmId, result.getAlarm().getUuidId());
}
@Test
public void testCantCreateAlarmIfCreateIsDisabled() {
TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
DeviceId deviceId = new DeviceId(UUID.randomUUID());
CreateOrUpdateActiveAlarmRequest request = CreateOrUpdateActiveAlarmRequest.builder()
.tenantId(tenantId)
.originator(deviceId)
.type("ALARM_TYPE")
.severity(AlarmSeverity.MAJOR)
.build();
AlarmApiCallResult result = alarmDao.createOrUpdateActiveAlarm(request, false);
assertFalse(result.isSuccessful());
}
@Test
public void testAckAlarmProcedure() {
UUID tenantId = UUID.fromString("d4b68f40-3e96-11e7-a884-898080180d6b");
UUID tenantId = UUID.randomUUID();
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
AlarmInfo alarmInfo = alarmDao.acknowledgeAlarm(alarm.getTenantId(), alarm.getId());
assertNotNull(alarmInfo);
long ackTs = System.currentTimeMillis();
AlarmApiCallResult result = alarmDao.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), ackTs);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(ackTs, result.getAlarm().getAckTs());
assertTrue(result.getAlarm().isAcknowledged());
result = alarmDao.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), ackTs + 1);
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isModified());
assertEquals(ackTs, result.getAlarm().getAckTs());
assertTrue(result.getAlarm().isAcknowledged());
}
@Test
public void testClearAlarmProcedure() {
UUID tenantId = UUID.randomUUID();
;
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
long clearTs = System.currentTimeMillis();
AlarmApiCallResult result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs, null);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCleared());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isCleared());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
}
@Test
public void testAssignAlarmProcedure() {
UUID tenantId = UUID.randomUUID();
;
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarmId = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
UserId userId1 = new UserId(UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d7b"));
UserId userId2 = new UserId(UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d8b"));
Alarm alarm = saveAlarm(alarmId, tenantId, originator1Id, "TEST_ALARM");
long assignTs = System.currentTimeMillis();
AlarmApiCallResult result = alarmDao.assignAlarm(alarm.getTenantId(), alarm.getId(), userId1, assignTs);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(assignTs, result.getAlarm().getAssignTs());
assertNotNull(result.getAlarm().getAssigneeId());
assertEquals(userId1, result.getAlarm().getAssigneeId());
result = alarmDao.assignAlarm(alarm.getTenantId(), alarm.getId(), userId1, assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isModified());
assertEquals(assignTs, result.getAlarm().getAssignTs());
assertNotNull(result.getAlarm().getAssigneeId());
assertEquals(userId1, result.getAlarm().getAssigneeId());
result = alarmDao.assignAlarm(alarm.getTenantId(), alarm.getId(), userId2, assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(assignTs + 1, result.getAlarm().getAssignTs());
assertNotNull(result.getAlarm().getAssigneeId());
assertEquals(userId2, result.getAlarm().getAssigneeId());
result = alarmDao.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertNull(result.getAlarm().getAssigneeId());
result = alarmDao.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs + 1);
afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertFalse(result.isModified());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertNull(result.getAlarm().getAssigneeId());
}
private Alarm saveAlarm(UUID id, UUID tenantId, UUID deviceId, String type) {
@ -90,6 +299,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
alarm.setEndTs(System.currentTimeMillis());
alarm.setAcknowledged(false);
alarm.setCleared(false);
alarm.setDetails(JacksonUtil.newObjectNode().put("a", UUID.randomUUID().toString()).set("b", JacksonUtil.newObjectNode().put("a", "[}/.`1321421!@@$$(%&&$")));
return alarmDao.save(TenantId.fromUUID(tenantId), alarm);
}

41
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java

@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.CreateOrUpdateActiveAlarmRequest;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@ -32,6 +34,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Collection;
@ -41,19 +44,45 @@ import java.util.Collection;
*/
public interface RuleEngineAlarmService {
Alarm createOrUpdateAlarm(Alarm alarm);
/*
* New API, since 3.5.
*/
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
/**
* Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request);
/**
* Designed to update existing alarm. Accepts only part of the alarm fields.
*/
AlarmApiCallResult updateAlarm(AlarmUpdateRequest request);
AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details);
AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs);
AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
/*
* Legacy API, before 3.5.
*/
@Deprecated(since = "3.5", forRemoval = true)
Alarm createOrUpdateAlarm(Alarm alarm);
@Deprecated(since = "3.5", forRemoval = true)
ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
@Deprecated(since = "3.5", forRemoval = true)
ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
@Deprecated(since = "3.5", forRemoval = true)
ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs);
AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
// Other API
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
@ -63,7 +92,7 @@ public interface RuleEngineAlarmService {
AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId);
default ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId){
default ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) {
return Futures.immediateFuture(findAlarmInfoById(tenantId, alarmId));
}

23
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
@Slf4j
@RuleNode(
@ -74,22 +75,14 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
private ListenableFuture<TbAlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
ctx.logJsEvalRequest();
ListenableFuture<JsonNode> asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails());
return Futures.transformAsync(asyncDetails, details -> {
return Futures.transform(asyncDetails, details -> {
ctx.logJsEvalResponse();
ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), details, System.currentTimeMillis());
return Futures.transformAsync(clearFuture, cleared -> {
ListenableFuture<Alarm> savedAlarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId());
return Futures.transformAsync(savedAlarmFuture, savedAlarm -> {
if (cleared && savedAlarm != null) {
alarm.setDetails(savedAlarm.getDetails());
alarm.setEndTs(savedAlarm.getEndTs());
alarm.setClearTs(savedAlarm.getClearTs());
}
//TODO: remove and return the alarm from a DB call.
alarm.setCleared(true);
return Futures.immediateFuture(new TbAlarmResult(false, false, true, alarm));
}, ctx.getDbCallbackExecutor());
}, ctx.getDbCallbackExecutor());
AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), System.currentTimeMillis(), details);
if (result.isSuccessful()) {
return new TbAlarmResult(false, false, result.isCleared(), result.getAlarm());
} else {
return new TbAlarmResult(false, false, false, alarm);
}
}, ctx.getDbCallbackExecutor());
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java

@ -57,7 +57,7 @@ import java.util.List;
)
public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
private static ObjectMapper mapper = new ObjectMapper();
private static final ObjectMapper mapper = new ObjectMapper();
private List<String> relationTypes;
private AlarmSeverity notDynamicAlarmSeverity;

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

@ -17,12 +17,10 @@ package org.thingsboard.rule.engine.profile;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.action.TbAlarmResult;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState;
@ -30,16 +28,14 @@ import org.thingsboard.rule.engine.profile.state.PersistedAlarmState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import java.util.ArrayList;
import java.util.Comparator;
@ -127,16 +123,12 @@ class AlarmState {
for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
stateUpdate = clearAlarmState(stateUpdate, state);
}
ListenableFuture<AlarmOperationResult> alarmClearOperationResult = ctx.getAlarmService().clearAlarmForResult(
ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis()
AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(
ctx.getTenantId(), currentAlarm.getId(), System.currentTimeMillis(), createDetails(clearState)
);
DonAsynchron.withCallback(alarmClearOperationResult,
result -> {
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState);
},
throwable -> {
throw new RuntimeException(throwable);
});
if (result.isCleared()) {
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState);
}
currentAlarm = null;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) {
stateUpdate = clearAlarmState(stateUpdate, clearState);

Loading…
Cancel
Save