Browse Source

Alarm service refactroing

pull/7942/head
Andrii Shvaika 4 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 @ResponseBody
public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException {
alarm.setTenantId(getTenantId()); alarm.setTenantId(getTenantId());
checkNotNull(alarm.getOriginator());
checkEntity(alarm.getId(), alarm, Resource.ALARM); checkEntity(alarm.getId(), alarm, Resource.ALARM);
checkEntityId(alarm.getOriginator(), Operation.READ);
return tbAlarmService.save(alarm, getCurrentUser()); return tbAlarmService.save(alarm, getCurrentUser());
} }
@ -159,11 +161,12 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @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); checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); 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)", @ApiOperation(value = "Clear Alarm (clearAlarm)",
@ -173,11 +176,12 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @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); checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); 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)", @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; break;
case ALARM_ACK_RPC_MESSAGE: case ALARM_ACK_RPC_MESSAGE:
if (existentAlarm != null) { if (existentAlarm != null) {
alarmService.ackAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs()); alarmService.acknowledgeAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs());
} }
break; break;
case ALARM_CLEAR_RPC_MESSAGE: 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.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType; 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.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.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; 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.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -47,7 +53,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
TenantId tenantId = alarm.getTenantId(); TenantId tenantId = alarm.getTenantId();
try { try {
Alarm savedAlarm = checkNotNull(alarmSubscriptionService.createOrUpdateAlarm(alarm)); 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; return savedAlarm;
} catch (Exception e) { } catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e);
@ -56,59 +71,100 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
} }
@Override @Override
public ListenableFuture<Void> ack(Alarm alarm, User user) { public AlarmInfo ack(Alarm alarm, User user) throws ThingsboardException {
long ackTs = System.currentTimeMillis(); AlarmApiCallResult result = alarmSubscriptionService.acknowledgeAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis());
ListenableFuture<Boolean> future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs); if (!result.isSuccessful()) {
return Futures.transform(future, result -> { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
if (result.isModified()) {
AlarmComment alarmComment = AlarmComment.builder() AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId()) .alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM) .type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was acknowledged by user %s", .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())) (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())) .put("userId", user.getId().toString())
.put("subtype", "ACK"))
.build(); .build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment); alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
alarm.setAckTs(ackTs); notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ACK, user);
alarm.setAcknowledged(true); } else {
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user); throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
return null; }
}, MoreExecutors.directExecutor()); return result.getAlarm();
} }
@Override @Override
public ListenableFuture<Void> clear(Alarm alarm, User user) { public AlarmInfo clear(Alarm alarm, User user) throws ThingsboardException {
long clearTs = System.currentTimeMillis(); AlarmApiCallResult result = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis(), null);
ListenableFuture<Boolean> future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs); if (!result.isSuccessful()) {
return Futures.transform(future, result -> { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
}
if (result.isCleared()) {
AlarmComment alarmComment = AlarmComment.builder() AlarmComment alarmComment = AlarmComment.builder()
.alarmId(alarm.getId()) .alarmId(alarm.getId())
.type(AlarmCommentType.SYSTEM) .type(AlarmCommentType.SYSTEM)
.comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm was cleared by user %s", .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())) (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))
.put("userId", user.getId().toString())) .put("userId", user.getId().toString())
.put("subtype", "CLEAR"))
.build(); .build();
alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment); alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment);
alarm.setClearTs(clearTs); notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_CLEAR, user);
alarm.setCleared(true); } else {
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user); throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
return null; }
}, MoreExecutors.directExecutor()); return result.getAlarm();
} }
@Override @Override
public Alarm assign(Alarm alarm, User user, UserId assigneeId) { public AlarmInfo assign(Alarm alarm, User user, UserId assigneeId) throws ThingsboardException {
long assignTs = System.currentTimeMillis(); AlarmApiCallResult result = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, System.currentTimeMillis());
AlarmOperationResult operationResult = alarmSubscriptionService.assignAlarm(alarm.getTenantId(), alarm.getId(), assigneeId, assignTs); if (!result.isSuccessful()) {
notificationEntityService.notifyCreateOrUpdateAlarm(operationResult.getAlarm(), ActionType.ALARM_ASSIGN, user); throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
return operationResult.getAlarm(); }
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 @Override
public Alarm unassign(Alarm alarm, User user) { public AlarmInfo unassign(Alarm alarm, User user) throws ThingsboardException {
long assignTs = System.currentTimeMillis(); AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), System.currentTimeMillis());
AlarmOperationResult operationResult = alarmSubscriptionService.unassignAlarm(alarm.getTenantId(), alarm.getId(), assignTs); if (!result.isSuccessful()) {
notificationEntityService.notifyCreateOrUpdateAlarm(operationResult.getAlarm(), ActionType.ALARM_UNASSIGN, user); throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
return operationResult.getAlarm(); }
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 @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; 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.User;
import org.thingsboard.server.common.data.alarm.Alarm; 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.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
@ -25,13 +25,13 @@ public interface TbAlarmService {
Alarm save(Alarm entity, User user) throws ThingsboardException; 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); 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.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.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.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; 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.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; 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.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
@ -93,6 +96,37 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return "alarm"; 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 @Override
public Alarm createOrUpdateAlarm(Alarm alarm) { public Alarm createOrUpdateAlarm(Alarm alarm) {
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled()); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
@ -123,44 +157,22 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
@Override @Override
public ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) { 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); Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor);
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); return Futures.transform(result, AlarmApiCallResult::isSuccessful, wsCallBackExecutor);
} }
@Override @Override
public ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { public ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
ListenableFuture<AlarmOperationResult> result = clearAlarmForResult(tenantId, alarmId, details, clearTs); AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); return Futures.transform(Futures.immediateFuture(result), AlarmApiCallResult::isSuccessful, wsCallBackExecutor);
} }
@Override @Override
public ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { public ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) {
ListenableFuture<AlarmOperationResult> result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs); AlarmApiCallResult result = alarmService.clearAlarm(tenantId, alarmId, clearTs, details);
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); Futures.addCallback(Futures.immediateFuture(result), new AlarmUpdateCallback(), wsCallBackExecutor);
return result; return Futures.immediateFuture(new AlarmOperationResult(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;
} }
@Override @Override
@ -203,7 +215,28 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return alarmService.findLatestByOriginatorAndType(tenantId, originator, type); return alarmService.findLatestByOriginatorAndType(tenantId, originator, type);
} }
@Deprecated
private void onAlarmUpdated(AlarmOperationResult result) { 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(() -> { wsCallBackExecutor.submit(() -> {
Alarm alarm = result.getAlarm(); Alarm alarm = result.getAlarm();
TenantId tenantId = alarm.getTenantId(); 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 @Override
public void onSuccess(@Nullable AlarmOperationResult result) { public void onSuccess(@Nullable AlarmApiCallResult result) {
onAlarmUpdated(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.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatus; 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.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId; 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.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
@ -83,15 +85,18 @@ public class DefaultTbAlarmServiceTest {
@Test @Test
public void testSave() throws ThingsboardException { public void testSave() throws ThingsboardException {
var alarm = new AlarmInfo(); 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()); service.save(alarm, new User());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
verify(alarmSubscriptionService, times(1)).createOrUpdateAlarm(eq(alarm)); verify(alarmSubscriptionService, times(1)).createAlarm(any());
} }
@Test @Test
public void testAck() { public void testAck() throws ThingsboardException {
var alarm = new Alarm(); var alarm = new Alarm();
when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
service.ack(alarm, new User(new UserId(UUID.randomUUID()))); service.ack(alarm, new User(new UserId(UUID.randomUUID())));
@ -102,7 +107,7 @@ public class DefaultTbAlarmServiceTest {
} }
@Test @Test
public void testClear() { public void testClear() throws ThingsboardException {
var alarm = new Alarm(); var alarm = new Alarm();
alarm.setAcknowledged(true); alarm.setAcknowledged(true);
when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(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; package org.thingsboard.server.dao.alarm;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; 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.Collections;
import java.util.List; import java.util.List;
@Builder
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@Deprecated
public class AlarmOperationResult { public class AlarmOperationResult {
private final Alarm alarm; private final Alarm alarm;
private final boolean successful; private final boolean successful;
@ -34,18 +37,12 @@ public class AlarmOperationResult {
private final AlarmSeverity oldSeverity; private final AlarmSeverity oldSeverity;
private final List<EntityId> propagatedEntitiesList; 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) { public AlarmOperationResult(Alarm alarm, boolean successful) {
this(alarm, successful, Collections.emptyList()); this(alarm, successful, Collections.emptyList());
} }
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) { 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) { public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
@ -54,6 +51,14 @@ public class AlarmOperationResult {
this.created = created; this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList; this.propagatedEntitiesList = propagatedEntitiesList;
this.oldSeverity = null; 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 { public interface AlarmService extends EntityDaoService {
// New API, since 3.5. /*
* New API, since 3.5.
*/
/** /**
* Designed for atomic operations over active alarms. * Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType} * 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. * Designed for atomic operations over active alarms.
* Only one active alarm may exist for the pair {originatorId, alarmType} * 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. * 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); 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); ListenableFuture<AlarmOperationResult> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long ts); @Deprecated(since = "3.5.0", forRemoval = true)
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long ts);
// Other API
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(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; package org.thingsboard.server.common.data.alarm;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import java.io.Serializable; import java.io.Serializable;
@Builder
@AllArgsConstructor
@Data @Data
public class AlarmAssignee implements Serializable { 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; package org.thingsboard.server.common.data.alarm;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Collections;
import java.util.List; import java.util.List;
@Builder
@Data @Data
public class AlarmPropagationInfo { 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") @ApiModelProperty(position = 1, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate; 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") @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; private boolean propagateToOwner;
@ApiModelProperty(position = 3, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") @ApiModelProperty(position = 3, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant; private boolean propagateToTenant;
@NoXss
@ApiModelProperty(position = 4, value = "JSON array of relation types that should be used for propagation. " + @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. " + "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.") "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 com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId; 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 @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) @ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@NotNull
@ApiModelProperty(position = 2, value = "JSON object with the alarm Id. " + @ApiModelProperty(position = 2, value = "JSON object with the alarm Id. " +
"Specify this field to update the alarm. " + "Specify this field to update the alarm. " +
"Referencing non-existing alarm Id will cause error. " + "Referencing non-existing alarm Id will cause error. " +
"Omit this field to create new alarm.") "Omit this field to create new alarm.")
private AlarmId alarmId; private AlarmId alarmId;
@NotNull
@ApiModelProperty(position = 3, required = true, value = "Alarm severity", example = "CRITICAL") @ApiModelProperty(position = 3, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity; private AlarmSeverity severity;
@ApiModelProperty(position = 4, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") @ApiModelProperty(position = 4, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs; private long startTs;
@ApiModelProperty(position = 5, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") @ApiModelProperty(position = 5, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs; private long endTs;
@NoXss
@ApiModelProperty(position = 6, value = "JSON object with alarm details") @ApiModelProperty(position = 6, value = "JSON object with alarm details")
private JsonNode details; private JsonNode details;
@Valid
@ApiModelProperty(position = 7, value = "JSON object with propagation details") @ApiModelProperty(position = 7, value = "JSON object with propagation details")
private AlarmPropagationInfo propagation; 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 com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length; 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 @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) @ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@ApiModelProperty(position = 2, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 2, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId; private CustomerId customerId;
@NotNull
@ApiModelProperty(position = 3, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") @ApiModelProperty(position = 3, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@Length(fieldName = "type") @Length(fieldName = "type")
private String type; private String type;
@NotNull
@ApiModelProperty(position = 4, required = true, value = "JSON object with alarm originator id") @ApiModelProperty(position = 4, required = true, value = "JSON object with alarm originator id")
private EntityId originator; private EntityId originator;
@NotNull
@ApiModelProperty(position = 5, required = true, value = "Alarm severity", example = "CRITICAL") @ApiModelProperty(position = 5, required = true, value = "Alarm severity", example = "CRITICAL")
private AlarmSeverity severity; private AlarmSeverity severity;
@ApiModelProperty(position = 6, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") @ApiModelProperty(position = 6, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565")
private long startTs; private long startTs;
@ApiModelProperty(position = 7, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") @ApiModelProperty(position = 7, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs; private long endTs;
@NoXss
@ApiModelProperty(position = 8, value = "JSON object with alarm details") @ApiModelProperty(position = 8, value = "JSON object with alarm details")
private JsonNode details; private JsonNode details;
@Valid
@ApiModelProperty(position = 9, value = "JSON object with propagation details") @ApiModelProperty(position = 9, value = "JSON object with propagation details")
private AlarmPropagationInfo propagation; 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 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) { public static EntityId getByTypeAndId(String type, String uuid) {
return getByTypeAndUuid(EntityType.valueOf(type), UUID.fromString(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; package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; 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.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -72,5 +74,16 @@ public interface AlarmDao extends Dao<Alarm> {
void deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId); 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.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.EntityType; 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.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; 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.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.alarm.AlarmInfo; 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.alarm.EntityAlarm;
import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
import org.thingsboard.server.common.data.id.AlarmId; 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.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId; 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.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService; 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.service.DataValidator;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -77,34 +77,52 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("AlarmDaoService") @Service("AlarmDaoService")
@Slf4j @Slf4j
@RequiredArgsConstructor
public class BaseAlarmService extends AbstractEntityService implements AlarmService { public class BaseAlarmService extends AbstractEntityService implements AlarmService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
@Autowired private final TenantService tenantService;
private AlarmDao alarmDao; private final AlarmDao alarmDao;
private final EntityService entityService;
private final DataValidator<Alarm> alarmDataValidator;
@Autowired @Override
private EntityService entityService; public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
validateAlarmRequest(request);
@Autowired return withPropagated(alarmDao.updateAlarm(request));
private DataValidator<Alarm> alarmDataValidator; }
@Override
public AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request) {
return createAlarm(request, true);
}
@Override @Override
public AlarmOperationResult updateAlarm(AlarmUpdateRequest request) { public AlarmApiCallResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) {
return null; 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 @Override
public AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request) { public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) {
return null; return withPropagated(alarmDao.acknowledgeAlarm(tenantId, alarmId, ackTs));
} }
@Override @Override
public AlarmOperationResult createAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) { public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) {
return null; return withPropagated(alarmDao.clearAlarm(tenantId, alarmId, clearTs, details));
} }
@Override @Override
@ -154,6 +172,20 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmDao.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds); 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 @Override
@Transactional @Transactional
public AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId) { 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); 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<>(); Set<EntityId> propagatedEntitiesSet = new LinkedHashSet<>();
propagatedEntitiesSet.add(alarm.getOriginator()); propagatedEntitiesSet.add(alarm.getOriginator());
if (alarm.isPropagate()) { if (alarm.isPropagate()) {
@ -228,7 +260,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else { } else {
propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result)); propagatedEntitiesList = new ArrayList<>(getPropagationEntityIds(result));
} }
return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList, null); return new AlarmOperationResult(result, true, false, oldAlarmSeverity, propagatedEntitiesList);
} }
@Override @Override
@ -261,43 +293,13 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
@Override @Override
public AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) { public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) {
return getAndUpdate(tenantId, alarmId, new Function<>() { return withPropagated(alarmDao.assignAlarm(tenantId, alarmId, assigneeId, assignTime));
@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)));
}
}
});
} }
@Override @Override
public AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTime) { public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime) {
return getAndUpdate(tenantId, alarmId, new Function<>() { return withPropagated(alarmDao.unassignAlarm(tenantId, alarmId, unassignTime));
@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)));
}
}
});
} }
@Override @Override
@ -340,7 +342,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else if (alarmStatus != null) { } else if (alarmStatus != null) {
asf = AlarmStatusFilter.from(alarmStatus); asf = AlarmStatusFilter.from(alarmStatus);
} else { } else {
asf= AlarmStatusFilter.empty(); asf = AlarmStatusFilter.empty();
} }
Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, asf, assigneeId); Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, asf, assigneeId);
@ -391,6 +393,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return existing; return existing;
} }
private List<EntityId> getPropagationEntityIdsList(Alarm alarm) {
return new ArrayList<>(getPropagationEntityIds(alarm));
}
private Set<EntityId> getPropagationEntityIds(Alarm alarm) { private Set<EntityId> getPropagationEntityIds(Alarm alarm) {
if (alarm.isPropagate() || alarm.isPropagateToOwner() || alarm.isPropagateToTenant()) { if (alarm.isPropagate() || alarm.isPropagateToOwner() || alarm.isPropagateToTenant()) {
List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId()); List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId());
@ -425,4 +431,39 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return EntityType.ALARM; 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_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_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_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_STATUS_PROPERTY = "status";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id"; public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
public static final String ALARM_ORIGINATOR_NAME_PROPERTY = "originator_name"; 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_TO_TENANT_PROPERTY = "propagate_to_tenant";
public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types"; 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_BY_ID_VIEW_NAME = "alarm_by_id";
public static final String ALARM_COMMENT_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; 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; private Long assignTs;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.ALARM_DETAILS_PROPERTY)
private JsonNode details; private JsonNode details;
@Column(name = ALARM_PROPAGATE_PROPERTY) @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) @EqualsAndHashCode(callSuper = true)
@Entity @Entity
@Table(name = ALARM_VIEW_NAME) @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> { public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
@Column(name = ALARM_ORIGINATOR_NAME_PROPERTY) @Column(name = ALARM_ORIGINATOR_NAME_PROPERTY)
@ -73,16 +66,6 @@ public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
super(); super();
} }
public AlarmInfoEntity(AlarmEntity alarmEntity,
String assigneeFirstName,
String assigneeLastName,
String assigneeEmail) {
super(alarmEntity);
this.assigneeFirstName = assigneeFirstName;
this.assigneeLastName = assigneeLastName;
this.assigneeEmail = assigneeEmail;
}
@Override @Override
public AlarmInfo toData() { public AlarmInfo toData() {
AlarmInfo alarmInfo = new AlarmInfo(super.toAlarm()); 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.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; 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;
import org.springframework.data.jpa.repository.query.Procedure; import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.AlarmInfoEntity; 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") @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); AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId);
// See named native query for definition @Procedure(procedureName = "create_or_update_active_alarm")
AlarmInfoEntity acknowledgeAlarm(@Param("t_id") UUID tenantId, String createOrUpdateActiveAlarm(@Param("t_id") UUID tenantId, @Param("c_id") UUID customerId,
@Param("a_id") UUID alarmId, @Param("a_id") UUID alarmId, @Param("a_created_ts") long createdTime,
@Param("a_ts") long ts); @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; package org.thingsboard.server.dao.sql.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.dialect.Dialect; import org.jetbrains.annotations.NotNull;
import org.hibernate.type.UUIDCharType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; 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.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm; 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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmPropagationInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; 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.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; 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.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; 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.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.DaoUtil; 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.alarm.AlarmDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.query.AlarmQueryRepository; import org.thingsboard.server.dao.sql.query.AlarmQueryRepository;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import javax.persistence.EntityManager; import java.util.Arrays;
import javax.persistence.PersistenceContext;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -222,8 +231,171 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
} }
@Override @Override
public AlarmInfo acknowledgeAlarm(TenantId tenantId, AlarmId id) { public AlarmApiCallResult createOrUpdateActiveAlarm(CreateOrUpdateActiveAlarmRequest request, boolean alarmCreationEnabled) {
return DaoUtil.getData(alarmRepository.acknowledgeAlarm(tenantId.getId(), id.getId(), System.currentTimeMillis())); 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 @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_created_time ON alarm(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time_active CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type_active
ON alarm USING btree (tenant_id, type, created_time DESC) WHERE cleared = false; 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); 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 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 CREATE VIEW alarm_info AS
SELECT a.*, SELECT a.*,
(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK' (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 FROM alarm a
LEFT JOIN tb_user u ON u.id = a.assignee_id; 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; DROP FUNCTION IF EXISTS acknowledge_alarm;
CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint) CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
RETURNS alarm_info LANGUAGE plpgsql RETURNS varchar
AS $$ LANGUAGE plpgsql
AS
$$
DECLARE
existing alarm;
result alarm_info;
modified boolean = FALSE;
BEGIN 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; SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
RETURN (SELECT a FROM alarm_info a WHERE a.tenant_id = t_id AND id = a_id); IF existing IS NULL THEN
END; 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.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Arrays; import java.util.Arrays;
@ -188,7 +189,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0))); 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(); created = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
@ -257,7 +258,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertNotNull(tenantUser); 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(); created = assignmentResult.getAlarm();
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() 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(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0))); 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.setPage(0);
pageLink.setPageSize(10); 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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; 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.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; 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.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; 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.AbstractJpaDaoTest;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; 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.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/** /**
* Created by Valerii Sosliuk on 5/21/2017. * Created by Valerii Sosliuk on 5/21/2017.
@ -48,35 +60,232 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test @Test
public void testFindLatestByOriginatorAndType() throws ExecutionException, InterruptedException, TimeoutException { public void testFindLatestByOriginatorAndType() throws ExecutionException, InterruptedException, TimeoutException {
log.info("Current system time in millis = {}", System.currentTimeMillis()); 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 originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID originator2Id = UUID.fromString("d4b68f42-3e96-11e7-a884-898080180d6b"); UUID originator2Id = UUID.fromString("d4b68f42-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
UUID alarm2Id = UUID.fromString("d4b68f44-3e96-11e7-a884-898080180d6b"); UUID alarm2Id = UUID.fromString("d4b68f44-3e96-11e7-a884-898080180d6b");
UUID alarm3Id = UUID.fromString("d4b68f45-3e96-11e7-a884-898080180d6b"); UUID alarm3Id = UUID.fromString("d4b68f45-3e96-11e7-a884-898080180d6b");
int alarmCountBeforeSave = alarmDao.find(TenantId.fromUUID(tenantId)).size(); // The find method does not filter by tenant. It is just using the tenantId for rate limits if any.
saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); 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 //The timestamp of the startTime should be different in order for test to always work
Thread.sleep(1); Thread.sleep(1);
saveAlarm(alarm2Id, tenantId, originator1Id, "TEST_ALARM"); saveAlarm(alarm2Id, tenantId.getId(), originator1Id, "TEST_ALARM");
saveAlarm(alarm3Id, tenantId, originator2Id, "TEST_ALARM"); saveAlarm(alarm3Id, tenantId.getId(), originator2Id, "TEST_ALARM");
int alarmCountAfterSave = alarmDao.find(TenantId.fromUUID(tenantId)).size(); var alarmsAfterSave = alarmDao.find(tenantId).stream().filter(a -> a.getTenantId().equals(tenantId)).collect(Collectors.toList());
assertEquals(3, alarmCountAfterSave - alarmCountBeforeSave); int alarmCountAfterSave = alarmsAfterSave.size();
int diff = alarmCountAfterSave - alarmCountBeforeSave;
if (diff != 3) {
System.out.println("test");
}
assertEquals(3, diff);
ListenableFuture<Alarm> future = alarmDao 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); Alarm alarm = future.get(30, TimeUnit.SECONDS);
assertNotNull(alarm); assertNotNull(alarm);
assertEquals(alarm2Id, alarm.getId().getId()); 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 @Test
public void testAckAlarmProcedure() { 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 originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
AlarmInfo alarmInfo = alarmDao.acknowledgeAlarm(alarm.getTenantId(), alarm.getId()); long ackTs = System.currentTimeMillis();
assertNotNull(alarmInfo); 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) { 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.setEndTs(System.currentTimeMillis());
alarm.setAcknowledged(false); alarm.setAcknowledged(false);
alarm.setCleared(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); 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.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.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.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; 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.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Collection; import java.util.Collection;
@ -41,19 +44,45 @@ import java.util.Collection;
*/ */
public interface RuleEngineAlarmService { 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); 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); 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); ListenableFuture<AlarmOperationResult> clearAlarmForResult(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs);
AlarmOperationResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTs); // Other API
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
AlarmOperationResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long assignTs);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId);
@ -63,7 +92,7 @@ public interface RuleEngineAlarmService {
AlarmInfo findAlarmInfoById(TenantId tenantId, AlarmId alarmId); 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)); 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.id.AlarmId;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -74,22 +75,14 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
private ListenableFuture<TbAlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { private ListenableFuture<TbAlarmResult> clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
ctx.logJsEvalRequest(); ctx.logJsEvalRequest();
ListenableFuture<JsonNode> asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails()); ListenableFuture<JsonNode> asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails());
return Futures.transformAsync(asyncDetails, details -> { return Futures.transform(asyncDetails, details -> {
ctx.logJsEvalResponse(); ctx.logJsEvalResponse();
ListenableFuture<Boolean> clearFuture = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), details, System.currentTimeMillis()); AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), System.currentTimeMillis(), details);
return Futures.transformAsync(clearFuture, cleared -> { if (result.isSuccessful()) {
ListenableFuture<Alarm> savedAlarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); return new TbAlarmResult(false, false, result.isCleared(), result.getAlarm());
return Futures.transformAsync(savedAlarmFuture, savedAlarm -> { } else {
if (cleared && savedAlarm != null) { return new TbAlarmResult(false, false, false, alarm);
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());
}, ctx.getDbCallbackExecutor()); }, 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> { public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
private static ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
private List<String> relationTypes; private List<String> relationTypes;
private AlarmSeverity notDynamicAlarmSeverity; 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.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.action.TbAlarmResult; import org.thingsboard.rule.engine.action.TbAlarmResult;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; 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.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; 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.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EntityId; 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.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; 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.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@ -127,16 +123,12 @@ class AlarmState {
for (AlarmRuleState state : createRulesSortedBySeverityDesc) { for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
stateUpdate = clearAlarmState(stateUpdate, state); stateUpdate = clearAlarmState(stateUpdate, state);
} }
ListenableFuture<AlarmOperationResult> alarmClearOperationResult = ctx.getAlarmService().clearAlarmForResult( AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(
ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis() ctx.getTenantId(), currentAlarm.getId(), System.currentTimeMillis(), createDetails(clearState)
); );
DonAsynchron.withCallback(alarmClearOperationResult, if (result.isCleared()) {
result -> { pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState);
pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState); }
},
throwable -> {
throw new RuntimeException(throwable);
});
currentAlarm = null; currentAlarm = null;
} else if (AlarmEvalResult.FALSE.equals(evalResult)) { } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
stateUpdate = clearAlarmState(stateUpdate, clearState); stateUpdate = clearAlarmState(stateUpdate, clearState);

Loading…
Cancel
Save