diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java new file mode 100644 index 0000000000..7d29d57dc9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.alarm.AlarmQuery; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.asset.AssetSearchQuery; +import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.exception.ThingsboardErrorCode; +import org.thingsboard.server.exception.ThingsboardException; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api") +public class AlarmController extends BaseController { + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET) + @ResponseBody + public Alarm getAlarmById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException { + checkParameter("alarmId", strAlarmId); + try { + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + return checkAlarmId(alarmId); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm", method = RequestMethod.POST) + @ResponseBody + public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException { + try { + alarm.setTenantId(getCurrentUser().getTenantId()); + return checkNotNull(alarmService.createOrUpdateAlarm(alarm)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void ackAlarm(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException { + checkParameter("alarmId", strAlarmId); + try { + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + checkAlarmId(alarmId); + alarmService.ackAlarm(alarmId, System.currentTimeMillis()).get(); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void clearAlarm(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException { + checkParameter("alarmId", strAlarmId); + try { + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + checkAlarmId(alarmId); + alarmService.clearAlarm(alarmId, System.currentTimeMillis()).get(); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) + @ResponseBody + public TimePageData getAlarms( + @PathVariable("entityType") String strEntityType, + @PathVariable("entityId") String strEntityId, + @RequestParam(required = false) String status, + @RequestParam int limit, + @RequestParam(required = false) Long startTime, + @RequestParam(required = false) Long endTime, + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, + @RequestParam(required = false) String offset + ) throws ThingsboardException { + checkParameter("EntityId", strEntityId); + checkParameter("EntityType", strEntityType); + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); + checkEntityId(entityId); + try { + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmStatus)).get()); + } catch (Exception e) { + throw handleException(e); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index d4adebeccc..ec93d29e00 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -25,6 +25,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.*; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.page.TextPageLink; @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; @@ -83,6 +86,9 @@ public abstract class BaseController { @Autowired protected AssetService assetService; + @Autowired + protected AlarmService alarmService; + @Autowired protected DeviceCredentialsService deviceCredentialsService; @@ -334,6 +340,22 @@ public abstract class BaseController { } } + Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException { + try { + validateId(alarmId, "Incorrect alarmId " + alarmId); + Alarm alarm = alarmService.findAlarmById(alarmId).get(); + checkAlarm(alarm); + return alarm; + } catch (Exception e) { + throw handleException(e, false); + } + } + + protected void checkAlarm(Alarm alarm) throws ThingsboardException { + checkNotNull(alarm); + checkTenantId(alarm.getTenantId()); + } + WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException { try { validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index 9c4f9218fc..00ca6c34ab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.alarm; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.EntityId; @@ -26,9 +27,9 @@ import org.thingsboard.server.common.data.page.TimePageLink; */ @Data @Builder +@AllArgsConstructor public class AlarmQuery { - private TenantId tenantId; private EntityId affectedEntityId; private TimePageLink pageLink; private AlarmStatus status; diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 3d35723afb..3c4da18138 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -28,8 +28,6 @@ public interface AlarmService { Alarm createOrUpdateAlarm(Alarm alarm); - ListenableFuture updateAlarm(Alarm alarm); - ListenableFuture ackAlarm(AlarmId alarmId, long ackTs); ListenableFuture clearAlarm(AlarmId alarmId, long ackTs); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 2675fa2cec..a5bf27c48d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -82,7 +82,6 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService } } - @Override public Alarm createOrUpdateAlarm(Alarm alarm) { alarmDataValidator.validate(alarm); @@ -93,53 +92,61 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService if (alarm.getEndTs() == 0L) { alarm.setEndTs(alarm.getStartTs()); } - Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); - if (existing == null || existing.getStatus().isCleared()) { - log.debug("New Alarm : {}", alarm); - Alarm saved = getData(alarmDao.save(new AlarmEntity(alarm))); - EntityRelationsQuery query = new EntityRelationsQuery(); - query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); - List parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); - for (EntityId parentId : parentEntities) { - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION)); - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name())); + if (alarm.getId() == null) { + Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); + if (existing == null || existing.getStatus().isCleared()) { + return createAlarm(alarm); + } else { + return updateAlarm(existing, alarm); } - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION)); - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name())); - return saved; } else { - log.debug("Alarm before merge: {}", alarm); - alarm = merge(existing, alarm); - log.debug("Alarm after merge: {}", alarm); - return getData(alarmDao.save(new AlarmEntity(alarm))); + return updateAlarm(alarm).get(); } } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } } - @Override - public ListenableFuture updateAlarm(Alarm update) { + private Alarm createAlarm(Alarm alarm) throws InterruptedException, ExecutionException { + log.debug("New Alarm : {}", alarm); + Alarm saved = getData(alarmDao.save(new AlarmEntity(alarm))); + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); + List parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); + for (EntityId parentId : parentEntities) { + createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION)); + createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name())); + } + createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION)); + createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name())); + return saved; + } + + protected ListenableFuture updateAlarm(Alarm update) { alarmDataValidator.validate(update); - return getAndUpdate(update.getId(), new Function() { + return getAndUpdate(update.getId(), new Function() { @Nullable @Override - public Boolean apply(@Nullable Alarm alarm) { + public Alarm apply(@Nullable Alarm alarm) { if (alarm == null) { - return false; + return null; } else { - AlarmStatus oldStatus = alarm.getStatus(); - AlarmStatus newStatus = update.getStatus(); - alarmDao.save(new AlarmEntity(merge(alarm, update))); - if (oldStatus != newStatus) { - updateRelations(alarm, oldStatus, newStatus); - } - return true; + return updateAlarm(alarm, update); } } }); } + private Alarm updateAlarm(Alarm oldAlarm, Alarm newAlarm) { + AlarmStatus oldStatus = oldAlarm.getStatus(); + AlarmStatus newStatus = newAlarm.getStatus(); + AlarmEntity result = alarmDao.save(new AlarmEntity(merge(oldAlarm, newAlarm))); + if (oldStatus != newStatus) { + updateRelations(oldAlarm, oldStatus, newStatus); + } + return result.toData(); + } + @Override public ListenableFuture ackAlarm(AlarmId alarmId, long ackTime) { return getAndUpdate(alarmId, new Function() { @@ -247,7 +254,7 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService } } - private ListenableFuture getAndUpdate(AlarmId alarmId, Function function) { + private ListenableFuture getAndUpdate(AlarmId alarmId, Function function) { validateId(alarmId, "Alarm id should be specified!"); ListenableFuture entity = alarmDao.findAlarmByIdAsync(alarmId.getId()); return Futures.transform(entity, function, readResultsProcessingExecutor); diff --git a/dao/src/main/resources/schema.cql b/dao/src/main/resources/schema.cql index 44afed9240..e0ef1e2536 100644 --- a/dao/src/main/resources/schema.cql +++ b/dao/src/main/resources/schema.cql @@ -276,7 +276,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.relation_by_type_and_child_ty from thingsboard.relation WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL PRIMARY KEY ((from_id, from_type), relation_type, to_type, to_id) - WITH CLUSTERING ORDER BY ( relation_type ASC, from_type ASC, from_id ASC); + WITH CLUSTERING ORDER BY ( relation_type ASC, to_type ASC, to_id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS SELECT * diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java index 4264cbd732..4bf43f917b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java @@ -117,20 +117,20 @@ public class AlarmServiceTest extends AbstractServiceTest { Alarm created = alarmService.createOrUpdateAlarm(alarm); // Check child relation - TimePageData alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId) + TimePageData alarms = alarmService.findAlarms(AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), true) + new TimePageLink(1, 0L, System.currentTimeMillis(), false) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(created, alarms.getData().get(0)); // Check parent relation - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId) + alarms = alarmService.findAlarms(AlarmQuery.builder() .affectedEntityId(parentId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), true) + new TimePageLink(1, 0L, System.currentTimeMillis(), false) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -139,20 +139,20 @@ public class AlarmServiceTest extends AbstractServiceTest { alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get(); created = alarmService.findAlarmById(created.getId()).get(); - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId) + alarms = alarmService.findAlarms(AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_ACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), true) + new TimePageLink(1, 0L, System.currentTimeMillis(), false) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(created, alarms.getData().get(0)); // Check not existing relation - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId) + alarms = alarmService.findAlarms(AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), true) + new TimePageLink(1, 0L, System.currentTimeMillis(), false) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(0, alarms.getData().size()); @@ -160,10 +160,10 @@ public class AlarmServiceTest extends AbstractServiceTest { alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get(); created = alarmService.findAlarmById(created.getId()).get(); - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId) + alarms = alarmService.findAlarms(AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.CLEARED_ACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), true) + new TimePageLink(1, 0L, System.currentTimeMillis(), false) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size());