Showing
7 changed files
with
202 additions
and
45 deletions
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()); | ... | ... |