Commit 10d39d2094909ad50cd88f5d6d353f7db37acb6e

Authored by Andrew Shvayka
1 parent 3143c706

Alarm REST API

  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.apache.commons.lang3.StringUtils;
  20 +import org.springframework.http.HttpStatus;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
  22 +import org.springframework.web.bind.annotation.*;
  23 +import org.thingsboard.server.common.data.Customer;
  24 +import org.thingsboard.server.common.data.Event;
  25 +import org.thingsboard.server.common.data.alarm.Alarm;
  26 +import org.thingsboard.server.common.data.alarm.AlarmId;
  27 +import org.thingsboard.server.common.data.alarm.AlarmQuery;
  28 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  29 +import org.thingsboard.server.common.data.asset.Asset;
  30 +import org.thingsboard.server.common.data.id.*;
  31 +import org.thingsboard.server.common.data.page.TextPageData;
  32 +import org.thingsboard.server.common.data.page.TextPageLink;
  33 +import org.thingsboard.server.common.data.page.TimePageData;
  34 +import org.thingsboard.server.common.data.page.TimePageLink;
  35 +import org.thingsboard.server.dao.asset.AssetSearchQuery;
  36 +import org.thingsboard.server.dao.exception.IncorrectParameterException;
  37 +import org.thingsboard.server.dao.model.ModelConstants;
  38 +import org.thingsboard.server.exception.ThingsboardErrorCode;
  39 +import org.thingsboard.server.exception.ThingsboardException;
  40 +import org.thingsboard.server.service.security.model.SecurityUser;
  41 +
  42 +import java.util.ArrayList;
  43 +import java.util.List;
  44 +import java.util.stream.Collectors;
  45 +
  46 +@RestController
  47 +@RequestMapping("/api")
  48 +public class AlarmController extends BaseController {
  49 +
  50 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  51 + @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET)
  52 + @ResponseBody
  53 + public Alarm getAlarmById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
  54 + checkParameter("alarmId", strAlarmId);
  55 + try {
  56 + AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
  57 + return checkAlarmId(alarmId);
  58 + } catch (Exception e) {
  59 + throw handleException(e);
  60 + }
  61 + }
  62 +
  63 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  64 + @RequestMapping(value = "/alarm", method = RequestMethod.POST)
  65 + @ResponseBody
  66 + public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
  67 + try {
  68 + alarm.setTenantId(getCurrentUser().getTenantId());
  69 + return checkNotNull(alarmService.createOrUpdateAlarm(alarm));
  70 + } catch (Exception e) {
  71 + throw handleException(e);
  72 + }
  73 + }
  74 +
  75 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  76 + @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
  77 + @ResponseStatus(value = HttpStatus.OK)
  78 + public void ackAlarm(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
  79 + checkParameter("alarmId", strAlarmId);
  80 + try {
  81 + AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
  82 + checkAlarmId(alarmId);
  83 + alarmService.ackAlarm(alarmId, System.currentTimeMillis()).get();
  84 + } catch (Exception e) {
  85 + throw handleException(e);
  86 + }
  87 + }
  88 +
  89 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  90 + @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
  91 + @ResponseStatus(value = HttpStatus.OK)
  92 + public void clearAlarm(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
  93 + checkParameter("alarmId", strAlarmId);
  94 + try {
  95 + AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
  96 + checkAlarmId(alarmId);
  97 + alarmService.clearAlarm(alarmId, System.currentTimeMillis()).get();
  98 + } catch (Exception e) {
  99 + throw handleException(e);
  100 + }
  101 + }
  102 +
  103 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  104 + @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
  105 + @ResponseBody
  106 + public TimePageData<Alarm> getAlarms(
  107 + @PathVariable("entityType") String strEntityType,
  108 + @PathVariable("entityId") String strEntityId,
  109 + @RequestParam(required = false) String status,
  110 + @RequestParam int limit,
  111 + @RequestParam(required = false) Long startTime,
  112 + @RequestParam(required = false) Long endTime,
  113 + @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
  114 + @RequestParam(required = false) String offset
  115 + ) throws ThingsboardException {
  116 + checkParameter("EntityId", strEntityId);
  117 + checkParameter("EntityType", strEntityType);
  118 + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
  119 + AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status);
  120 + checkEntityId(entityId);
  121 + try {
  122 + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
  123 + return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmStatus)).get());
  124 + } catch (Exception e) {
  125 + throw handleException(e);
  126 + }
  127 + }
  128 +
  129 +}
... ...
... ... @@ -25,6 +25,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
25 25 import org.springframework.web.bind.annotation.ExceptionHandler;
26 26 import org.thingsboard.server.actors.service.ActorService;
27 27 import org.thingsboard.server.common.data.*;
  28 +import org.thingsboard.server.common.data.alarm.Alarm;
  29 +import org.thingsboard.server.common.data.alarm.AlarmId;
28 30 import org.thingsboard.server.common.data.asset.Asset;
29 31 import org.thingsboard.server.common.data.id.*;
30 32 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData;
36 38 import org.thingsboard.server.common.data.security.Authority;
37 39 import org.thingsboard.server.common.data.widget.WidgetType;
38 40 import org.thingsboard.server.common.data.widget.WidgetsBundle;
  41 +import org.thingsboard.server.dao.alarm.AlarmService;
39 42 import org.thingsboard.server.dao.asset.AssetService;
40 43 import org.thingsboard.server.dao.customer.CustomerService;
41 44 import org.thingsboard.server.dao.dashboard.DashboardService;
... ... @@ -84,6 +87,9 @@ public abstract class BaseController {
84 87 protected AssetService assetService;
85 88
86 89 @Autowired
  90 + protected AlarmService alarmService;
  91 +
  92 + @Autowired
87 93 protected DeviceCredentialsService deviceCredentialsService;
88 94
89 95 @Autowired
... ... @@ -334,6 +340,22 @@ public abstract class BaseController {
334 340 }
335 341 }
336 342
  343 + Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException {
  344 + try {
  345 + validateId(alarmId, "Incorrect alarmId " + alarmId);
  346 + Alarm alarm = alarmService.findAlarmById(alarmId).get();
  347 + checkAlarm(alarm);
  348 + return alarm;
  349 + } catch (Exception e) {
  350 + throw handleException(e, false);
  351 + }
  352 + }
  353 +
  354 + protected void checkAlarm(Alarm alarm) throws ThingsboardException {
  355 + checkNotNull(alarm);
  356 + checkTenantId(alarm.getTenantId());
  357 + }
  358 +
337 359 WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException {
338 360 try {
339 361 validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data.alarm;
17 17
  18 +import lombok.AllArgsConstructor;
18 19 import lombok.Builder;
19 20 import lombok.Data;
20 21 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -26,9 +27,9 @@ import org.thingsboard.server.common.data.page.TimePageLink;
26 27 */
27 28 @Data
28 29 @Builder
  30 +@AllArgsConstructor
29 31 public class AlarmQuery {
30 32
31   - private TenantId tenantId;
32 33 private EntityId affectedEntityId;
33 34 private TimePageLink pageLink;
34 35 private AlarmStatus status;
... ...
... ... @@ -28,8 +28,6 @@ public interface AlarmService {
28 28
29 29 Alarm createOrUpdateAlarm(Alarm alarm);
30 30
31   - ListenableFuture<Boolean> updateAlarm(Alarm alarm);
32   -
33 31 ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTs);
34 32
35 33 ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
... ...
... ... @@ -82,7 +82,6 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
82 82 }
83 83 }
84 84
85   -
86 85 @Override
87 86 public Alarm createOrUpdateAlarm(Alarm alarm) {
88 87 alarmDataValidator.validate(alarm);
... ... @@ -93,53 +92,61 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
93 92 if (alarm.getEndTs() == 0L) {
94 93 alarm.setEndTs(alarm.getStartTs());
95 94 }
96   - Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
97   - if (existing == null || existing.getStatus().isCleared()) {
98   - log.debug("New Alarm : {}", alarm);
99   - Alarm saved = getData(alarmDao.save(new AlarmEntity(alarm)));
100   - EntityRelationsQuery query = new EntityRelationsQuery();
101   - query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
102   - List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
103   - for (EntityId parentId : parentEntities) {
104   - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION));
105   - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name()));
  95 + if (alarm.getId() == null) {
  96 + Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
  97 + if (existing == null || existing.getStatus().isCleared()) {
  98 + return createAlarm(alarm);
  99 + } else {
  100 + return updateAlarm(existing, alarm);
106 101 }
107   - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION));
108   - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name()));
109   - return saved;
110 102 } else {
111   - log.debug("Alarm before merge: {}", alarm);
112   - alarm = merge(existing, alarm);
113   - log.debug("Alarm after merge: {}", alarm);
114   - return getData(alarmDao.save(new AlarmEntity(alarm)));
  103 + return updateAlarm(alarm).get();
115 104 }
116 105 } catch (ExecutionException | InterruptedException e) {
117 106 throw new RuntimeException(e);
118 107 }
119 108 }
120 109
121   - @Override
122   - public ListenableFuture<Boolean> updateAlarm(Alarm update) {
  110 + private Alarm createAlarm(Alarm alarm) throws InterruptedException, ExecutionException {
  111 + log.debug("New Alarm : {}", alarm);
  112 + Alarm saved = getData(alarmDao.save(new AlarmEntity(alarm)));
  113 + EntityRelationsQuery query = new EntityRelationsQuery();
  114 + query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
  115 + List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
  116 + for (EntityId parentId : parentEntities) {
  117 + createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION));
  118 + createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name()));
  119 + }
  120 + createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION));
  121 + createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name()));
  122 + return saved;
  123 + }
  124 +
  125 + protected ListenableFuture<Alarm> updateAlarm(Alarm update) {
123 126 alarmDataValidator.validate(update);
124   - return getAndUpdate(update.getId(), new Function<Alarm, Boolean>() {
  127 + return getAndUpdate(update.getId(), new Function<Alarm, Alarm>() {
125 128 @Nullable
126 129 @Override
127   - public Boolean apply(@Nullable Alarm alarm) {
  130 + public Alarm apply(@Nullable Alarm alarm) {
128 131 if (alarm == null) {
129   - return false;
  132 + return null;
130 133 } else {
131   - AlarmStatus oldStatus = alarm.getStatus();
132   - AlarmStatus newStatus = update.getStatus();
133   - alarmDao.save(new AlarmEntity(merge(alarm, update)));
134   - if (oldStatus != newStatus) {
135   - updateRelations(alarm, oldStatus, newStatus);
136   - }
137   - return true;
  134 + return updateAlarm(alarm, update);
138 135 }
139 136 }
140 137 });
141 138 }
142 139
  140 + private Alarm updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
  141 + AlarmStatus oldStatus = oldAlarm.getStatus();
  142 + AlarmStatus newStatus = newAlarm.getStatus();
  143 + AlarmEntity result = alarmDao.save(new AlarmEntity(merge(oldAlarm, newAlarm)));
  144 + if (oldStatus != newStatus) {
  145 + updateRelations(oldAlarm, oldStatus, newStatus);
  146 + }
  147 + return result.toData();
  148 + }
  149 +
143 150 @Override
144 151 public ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTime) {
145 152 return getAndUpdate(alarmId, new Function<Alarm, Boolean>() {
... ... @@ -247,7 +254,7 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
247 254 }
248 255 }
249 256
250   - private ListenableFuture<Boolean> getAndUpdate(AlarmId alarmId, Function<Alarm, Boolean> function) {
  257 + private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) {
251 258 validateId(alarmId, "Alarm id should be specified!");
252 259 ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId());
253 260 return Futures.transform(entity, function, readResultsProcessingExecutor);
... ...
... ... @@ -276,7 +276,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.relation_by_type_and_child_ty
276 276 from thingsboard.relation
277 277 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
278 278 PRIMARY KEY ((from_id, from_type), relation_type, to_type, to_id)
279   - WITH CLUSTERING ORDER BY ( relation_type ASC, from_type ASC, from_id ASC);
  279 + WITH CLUSTERING ORDER BY ( relation_type ASC, to_type ASC, to_id DESC);
280 280
281 281 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS
282 282 SELECT *
... ...
... ... @@ -117,20 +117,20 @@ public class AlarmServiceTest extends AbstractServiceTest {
117 117 Alarm created = alarmService.createOrUpdateAlarm(alarm);
118 118
119 119 // Check child relation
120   - TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId)
  120 + TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder()
121 121 .affectedEntityId(childId)
122 122 .status(AlarmStatus.ACTIVE_UNACK).pageLink(
123   - new TimePageLink(1, 0L, System.currentTimeMillis(), true)
  123 + new TimePageLink(1, 0L, System.currentTimeMillis(), false)
124 124 ).build()).get();
125 125 Assert.assertNotNull(alarms.getData());
126 126 Assert.assertEquals(1, alarms.getData().size());
127 127 Assert.assertEquals(created, alarms.getData().get(0));
128 128
129 129 // Check parent relation
130   - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId)
  130 + alarms = alarmService.findAlarms(AlarmQuery.builder()
131 131 .affectedEntityId(parentId)
132 132 .status(AlarmStatus.ACTIVE_UNACK).pageLink(
133   - new TimePageLink(1, 0L, System.currentTimeMillis(), true)
  133 + new TimePageLink(1, 0L, System.currentTimeMillis(), false)
134 134 ).build()).get();
135 135 Assert.assertNotNull(alarms.getData());
136 136 Assert.assertEquals(1, alarms.getData().size());
... ... @@ -139,20 +139,20 @@ public class AlarmServiceTest extends AbstractServiceTest {
139 139 alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get();
140 140 created = alarmService.findAlarmById(created.getId()).get();
141 141
142   - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId)
  142 + alarms = alarmService.findAlarms(AlarmQuery.builder()
143 143 .affectedEntityId(childId)
144 144 .status(AlarmStatus.ACTIVE_ACK).pageLink(
145   - new TimePageLink(1, 0L, System.currentTimeMillis(), true)
  145 + new TimePageLink(1, 0L, System.currentTimeMillis(), false)
146 146 ).build()).get();
147 147 Assert.assertNotNull(alarms.getData());
148 148 Assert.assertEquals(1, alarms.getData().size());
149 149 Assert.assertEquals(created, alarms.getData().get(0));
150 150
151 151 // Check not existing relation
152   - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId)
  152 + alarms = alarmService.findAlarms(AlarmQuery.builder()
153 153 .affectedEntityId(childId)
154 154 .status(AlarmStatus.ACTIVE_UNACK).pageLink(
155   - new TimePageLink(1, 0L, System.currentTimeMillis(), true)
  155 + new TimePageLink(1, 0L, System.currentTimeMillis(), false)
156 156 ).build()).get();
157 157 Assert.assertNotNull(alarms.getData());
158 158 Assert.assertEquals(0, alarms.getData().size());
... ... @@ -160,10 +160,10 @@ public class AlarmServiceTest extends AbstractServiceTest {
160 160 alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get();
161 161 created = alarmService.findAlarmById(created.getId()).get();
162 162
163   - alarms = alarmService.findAlarms(AlarmQuery.builder().tenantId(tenantId)
  163 + alarms = alarmService.findAlarms(AlarmQuery.builder()
164 164 .affectedEntityId(childId)
165 165 .status(AlarmStatus.CLEARED_ACK).pageLink(
166   - new TimePageLink(1, 0L, System.currentTimeMillis(), true)
  166 + new TimePageLink(1, 0L, System.currentTimeMillis(), false)
167 167 ).build()).get();
168 168 Assert.assertNotNull(alarms.getData());
169 169 Assert.assertEquals(1, alarms.getData().size());
... ...