Showing
24 changed files
with
1470 additions
and
71 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,6 +25,8 @@ import org.springframework.security.core.context.SecurityContextHolder; | ||
25 | import org.springframework.web.bind.annotation.ExceptionHandler; | 25 | import org.springframework.web.bind.annotation.ExceptionHandler; |
26 | import org.thingsboard.server.actors.service.ActorService; | 26 | import org.thingsboard.server.actors.service.ActorService; |
27 | import org.thingsboard.server.common.data.*; | 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 | import org.thingsboard.server.common.data.asset.Asset; | 30 | import org.thingsboard.server.common.data.asset.Asset; |
29 | import org.thingsboard.server.common.data.id.*; | 31 | import org.thingsboard.server.common.data.id.*; |
30 | import org.thingsboard.server.common.data.page.TextPageLink; | 32 | import org.thingsboard.server.common.data.page.TextPageLink; |
@@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData; | @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData; | ||
36 | import org.thingsboard.server.common.data.security.Authority; | 38 | import org.thingsboard.server.common.data.security.Authority; |
37 | import org.thingsboard.server.common.data.widget.WidgetType; | 39 | import org.thingsboard.server.common.data.widget.WidgetType; |
38 | import org.thingsboard.server.common.data.widget.WidgetsBundle; | 40 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
41 | +import org.thingsboard.server.dao.alarm.AlarmService; | ||
39 | import org.thingsboard.server.dao.asset.AssetService; | 42 | import org.thingsboard.server.dao.asset.AssetService; |
40 | import org.thingsboard.server.dao.customer.CustomerService; | 43 | import org.thingsboard.server.dao.customer.CustomerService; |
41 | import org.thingsboard.server.dao.dashboard.DashboardService; | 44 | import org.thingsboard.server.dao.dashboard.DashboardService; |
@@ -84,6 +87,9 @@ public abstract class BaseController { | @@ -84,6 +87,9 @@ public abstract class BaseController { | ||
84 | protected AssetService assetService; | 87 | protected AssetService assetService; |
85 | 88 | ||
86 | @Autowired | 89 | @Autowired |
90 | + protected AlarmService alarmService; | ||
91 | + | ||
92 | + @Autowired | ||
87 | protected DeviceCredentialsService deviceCredentialsService; | 93 | protected DeviceCredentialsService deviceCredentialsService; |
88 | 94 | ||
89 | @Autowired | 95 | @Autowired |
@@ -334,6 +340,22 @@ public abstract class BaseController { | @@ -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.findAlarmByIdAsync(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 | WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException { | 359 | WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException { |
338 | try { | 360 | try { |
339 | validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); | 361 | validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); |
@@ -16,28 +16,43 @@ | @@ -16,28 +16,43 @@ | ||
16 | package org.thingsboard.server.common.data.alarm; | 16 | package org.thingsboard.server.common.data.alarm; |
17 | 17 | ||
18 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | +import lombok.AllArgsConstructor; | ||
20 | +import lombok.Builder; | ||
19 | import lombok.Data; | 21 | import lombok.Data; |
20 | import org.thingsboard.server.common.data.BaseData; | 22 | import org.thingsboard.server.common.data.BaseData; |
23 | +import org.thingsboard.server.common.data.id.AssetId; | ||
21 | import org.thingsboard.server.common.data.HasName; | 24 | import org.thingsboard.server.common.data.HasName; |
22 | import org.thingsboard.server.common.data.id.EntityId; | 25 | import org.thingsboard.server.common.data.id.EntityId; |
26 | +import org.thingsboard.server.common.data.id.TenantId; | ||
23 | 27 | ||
24 | /** | 28 | /** |
25 | * Created by ashvayka on 11.05.17. | 29 | * Created by ashvayka on 11.05.17. |
26 | */ | 30 | */ |
27 | @Data | 31 | @Data |
32 | +@Builder | ||
33 | +@AllArgsConstructor | ||
28 | public class Alarm extends BaseData<AlarmId> implements HasName { | 34 | public class Alarm extends BaseData<AlarmId> implements HasName { |
29 | 35 | ||
30 | - private long startTs; | ||
31 | - private long endTs; | ||
32 | - private long ackTs; | ||
33 | - private long clearTs; | 36 | + private TenantId tenantId; |
34 | private String type; | 37 | private String type; |
35 | private EntityId originator; | 38 | private EntityId originator; |
36 | private AlarmSeverity severity; | 39 | private AlarmSeverity severity; |
37 | private AlarmStatus status; | 40 | private AlarmStatus status; |
41 | + private long startTs; | ||
42 | + private long endTs; | ||
43 | + private long ackTs; | ||
44 | + private long clearTs; | ||
38 | private JsonNode details; | 45 | private JsonNode details; |
39 | private boolean propagate; | 46 | private boolean propagate; |
40 | 47 | ||
48 | + public Alarm() { | ||
49 | + super(); | ||
50 | + } | ||
51 | + | ||
52 | + public Alarm(AlarmId id) { | ||
53 | + super(id); | ||
54 | + } | ||
55 | + | ||
41 | @Override | 56 | @Override |
42 | public String getName() { | 57 | public String getName() { |
43 | return type; | 58 | return type; |
@@ -15,14 +15,19 @@ | @@ -15,14 +15,19 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.data.alarm; | 16 | package org.thingsboard.server.common.data.alarm; |
17 | 17 | ||
18 | +import lombok.AllArgsConstructor; | ||
19 | +import lombok.Builder; | ||
18 | import lombok.Data; | 20 | import lombok.Data; |
19 | import org.thingsboard.server.common.data.id.EntityId; | 21 | import org.thingsboard.server.common.data.id.EntityId; |
22 | +import org.thingsboard.server.common.data.id.TenantId; | ||
20 | import org.thingsboard.server.common.data.page.TimePageLink; | 23 | import org.thingsboard.server.common.data.page.TimePageLink; |
21 | 24 | ||
22 | /** | 25 | /** |
23 | * Created by ashvayka on 11.05.17. | 26 | * Created by ashvayka on 11.05.17. |
24 | */ | 27 | */ |
25 | @Data | 28 | @Data |
29 | +@Builder | ||
30 | +@AllArgsConstructor | ||
26 | public class AlarmQuery { | 31 | public class AlarmQuery { |
27 | 32 | ||
28 | private EntityId affectedEntityId; | 33 | private EntityId affectedEntityId; |
@@ -22,4 +22,12 @@ public enum AlarmStatus { | @@ -22,4 +22,12 @@ public enum AlarmStatus { | ||
22 | 22 | ||
23 | ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK; | 23 | ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK; |
24 | 24 | ||
25 | + public boolean isAck() { | ||
26 | + return this == ACTIVE_ACK || this == CLEARED_ACK; | ||
27 | + } | ||
28 | + | ||
29 | + public boolean isCleared() { | ||
30 | + return this == CLEARED_ACK || this == CLEARED_UNACK; | ||
31 | + } | ||
32 | + | ||
25 | } | 33 | } |
@@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
16 | package org.thingsboard.server.common.data.id; | 16 | package org.thingsboard.server.common.data.id; |
17 | 17 | ||
18 | import org.thingsboard.server.common.data.EntityType; | 18 | import org.thingsboard.server.common.data.EntityType; |
19 | +import org.thingsboard.server.common.data.alarm.AlarmId; | ||
19 | 20 | ||
20 | import java.util.UUID; | 21 | import java.util.UUID; |
21 | 22 | ||
@@ -50,6 +51,8 @@ public class EntityIdFactory { | @@ -50,6 +51,8 @@ public class EntityIdFactory { | ||
50 | return new DeviceId(uuid); | 51 | return new DeviceId(uuid); |
51 | case ASSET: | 52 | case ASSET: |
52 | return new AssetId(uuid); | 53 | return new AssetId(uuid); |
54 | + case ALARM: | ||
55 | + return new AlarmId(uuid); | ||
53 | } | 56 | } |
54 | throw new IllegalArgumentException("EntityType " + type + " is not supported!"); | 57 | throw new IllegalArgumentException("EntityType " + type + " is not supported!"); |
55 | } | 58 | } |
@@ -127,7 +127,7 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract | @@ -127,7 +127,7 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract | ||
127 | log.debug("Save entity {}", entity); | 127 | log.debug("Save entity {}", entity); |
128 | if (entity.getId() == null) { | 128 | if (entity.getId() == null) { |
129 | entity.setId(UUIDs.timeBased()); | 129 | entity.setId(UUIDs.timeBased()); |
130 | - } else { | 130 | + } else if (isDeleteOnSave()) { |
131 | removeById(entity.getId()); | 131 | removeById(entity.getId()); |
132 | } | 132 | } |
133 | Statement saveStatement = getSaveQuery(entity); | 133 | Statement saveStatement = getSaveQuery(entity); |
@@ -136,6 +136,10 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract | @@ -136,6 +136,10 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract | ||
136 | return new EntityResultSet<>(resultSet, entity); | 136 | return new EntityResultSet<>(resultSet, entity); |
137 | } | 137 | } |
138 | 138 | ||
139 | + protected boolean isDeleteOnSave() { | ||
140 | + return true; | ||
141 | + } | ||
142 | + | ||
139 | public T save(T entity) { | 143 | public T save(T entity) { |
140 | return saveWithResult(entity).getEntity(); | 144 | return saveWithResult(entity).getEntity(); |
141 | } | 145 | } |
@@ -161,9 +165,18 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract | @@ -161,9 +165,18 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract | ||
161 | return getSession().execute(delete); | 165 | return getSession().execute(delete); |
162 | } | 166 | } |
163 | 167 | ||
164 | - | ||
165 | public List<T> find() { | 168 | public List<T> find() { |
166 | log.debug("Get all entities from column family {}", getColumnFamilyName()); | 169 | log.debug("Get all entities from column family {}", getColumnFamilyName()); |
167 | return findListByStatement(QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel())); | 170 | return findListByStatement(QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel())); |
168 | } | 171 | } |
172 | + | ||
173 | + protected static <T> Function<BaseEntity<T>, T> toDataFunction() { | ||
174 | + return new Function<BaseEntity<T>, T>() { | ||
175 | + @Nullable | ||
176 | + @Override | ||
177 | + public T apply(@Nullable BaseEntity<T> entity) { | ||
178 | + return entity != null ? entity.toData() : null; | ||
179 | + } | ||
180 | + }; | ||
181 | + } | ||
169 | } | 182 | } |
@@ -47,8 +47,27 @@ public abstract class AbstractSearchTimeDao<T extends BaseEntity<?>> extends Abs | @@ -47,8 +47,27 @@ public abstract class AbstractSearchTimeDao<T extends BaseEntity<?>> extends Abs | ||
47 | return findPageWithTimeSearch(searchView, clauses, Collections.singletonList(ordering), pageLink); | 47 | return findPageWithTimeSearch(searchView, clauses, Collections.singletonList(ordering), pageLink); |
48 | } | 48 | } |
49 | 49 | ||
50 | - | ||
51 | protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink) { | 50 | protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink) { |
51 | + return findPageWithTimeSearch(searchView, clauses, topLevelOrderings, pageLink, ModelConstants.ID_PROPERTY); | ||
52 | + } | ||
53 | + | ||
54 | + protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, TimePageLink pageLink, String idColumn) { | ||
55 | + return findPageWithTimeSearch(searchView, clauses, Collections.emptyList(), pageLink, idColumn); | ||
56 | + } | ||
57 | + | ||
58 | + protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink, String idColumn) { | ||
59 | + return findListByStatement(buildQuery(searchView, clauses, topLevelOrderings, pageLink, idColumn)); | ||
60 | + } | ||
61 | + | ||
62 | + public static Where buildQuery(String searchView, List<Clause> clauses, TimePageLink pageLink, String idColumn) { | ||
63 | + return buildQuery(searchView, clauses, Collections.emptyList(), pageLink, idColumn); | ||
64 | + } | ||
65 | + | ||
66 | + public static Where buildQuery(String searchView, List<Clause> clauses, Ordering order, TimePageLink pageLink, String idColumn) { | ||
67 | + return buildQuery(searchView, clauses, Collections.singletonList(order), pageLink, idColumn); | ||
68 | + } | ||
69 | + | ||
70 | + public static Where buildQuery(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink, String idColumn) { | ||
52 | Select select = select().from(searchView); | 71 | Select select = select().from(searchView); |
53 | Where query = select.where(); | 72 | Where query = select.where(); |
54 | for (Clause clause : clauses) { | 73 | for (Clause clause : clauses) { |
@@ -57,34 +76,35 @@ public abstract class AbstractSearchTimeDao<T extends BaseEntity<?>> extends Abs | @@ -57,34 +76,35 @@ public abstract class AbstractSearchTimeDao<T extends BaseEntity<?>> extends Abs | ||
57 | query.limit(pageLink.getLimit()); | 76 | query.limit(pageLink.getLimit()); |
58 | if (pageLink.isAscOrder()) { | 77 | if (pageLink.isAscOrder()) { |
59 | if (pageLink.getIdOffset() != null) { | 78 | if (pageLink.getIdOffset() != null) { |
60 | - query.and(QueryBuilder.gt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset())); | 79 | + query.and(QueryBuilder.gt(idColumn, pageLink.getIdOffset())); |
61 | } else if (pageLink.getStartTime() != null) { | 80 | } else if (pageLink.getStartTime() != null) { |
62 | final UUID startOf = UUIDs.startOf(pageLink.getStartTime()); | 81 | final UUID startOf = UUIDs.startOf(pageLink.getStartTime()); |
63 | - query.and(QueryBuilder.gte(ModelConstants.ID_PROPERTY, startOf)); | 82 | + query.and(QueryBuilder.gte(idColumn, startOf)); |
64 | } | 83 | } |
65 | if (pageLink.getEndTime() != null) { | 84 | if (pageLink.getEndTime() != null) { |
66 | final UUID endOf = UUIDs.endOf(pageLink.getEndTime()); | 85 | final UUID endOf = UUIDs.endOf(pageLink.getEndTime()); |
67 | - query.and(QueryBuilder.lte(ModelConstants.ID_PROPERTY, endOf)); | 86 | + query.and(QueryBuilder.lte(idColumn, endOf)); |
68 | } | 87 | } |
69 | } else { | 88 | } else { |
70 | if (pageLink.getIdOffset() != null) { | 89 | if (pageLink.getIdOffset() != null) { |
71 | - query.and(QueryBuilder.lt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset())); | 90 | + query.and(QueryBuilder.lt(idColumn, pageLink.getIdOffset())); |
72 | } else if (pageLink.getEndTime() != null) { | 91 | } else if (pageLink.getEndTime() != null) { |
73 | final UUID endOf = UUIDs.endOf(pageLink.getEndTime()); | 92 | final UUID endOf = UUIDs.endOf(pageLink.getEndTime()); |
74 | - query.and(QueryBuilder.lte(ModelConstants.ID_PROPERTY, endOf)); | 93 | + query.and(QueryBuilder.lte(idColumn, endOf)); |
75 | } | 94 | } |
76 | if (pageLink.getStartTime() != null) { | 95 | if (pageLink.getStartTime() != null) { |
77 | final UUID startOf = UUIDs.startOf(pageLink.getStartTime()); | 96 | final UUID startOf = UUIDs.startOf(pageLink.getStartTime()); |
78 | - query.and(QueryBuilder.gte(ModelConstants.ID_PROPERTY, startOf)); | 97 | + query.and(QueryBuilder.gte(idColumn, startOf)); |
79 | } | 98 | } |
80 | } | 99 | } |
81 | List<Ordering> orderings = new ArrayList<>(topLevelOrderings); | 100 | List<Ordering> orderings = new ArrayList<>(topLevelOrderings); |
82 | if (pageLink.isAscOrder()) { | 101 | if (pageLink.isAscOrder()) { |
83 | - orderings.add(QueryBuilder.asc(ModelConstants.ID_PROPERTY)); | 102 | + orderings.add(QueryBuilder.asc(idColumn)); |
84 | } else { | 103 | } else { |
85 | - orderings.add(QueryBuilder.desc(ModelConstants.ID_PROPERTY)); | 104 | + orderings.add(QueryBuilder.desc(idColumn)); |
86 | } | 105 | } |
87 | query.orderBy(orderings.toArray(new Ordering[orderings.size()])); | 106 | query.orderBy(orderings.toArray(new Ordering[orderings.size()])); |
88 | - return findListByStatement(query); | 107 | + return query; |
89 | } | 108 | } |
109 | + | ||
90 | } | 110 | } |
@@ -15,8 +15,27 @@ | @@ -15,8 +15,27 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.dao.alarm; | 16 | package org.thingsboard.server.dao.alarm; |
17 | 17 | ||
18 | +import com.google.common.util.concurrent.ListenableFuture; | ||
19 | +import org.thingsboard.server.common.data.alarm.Alarm; | ||
20 | +import org.thingsboard.server.common.data.alarm.AlarmQuery; | ||
21 | +import org.thingsboard.server.common.data.id.EntityId; | ||
22 | +import org.thingsboard.server.common.data.id.TenantId; | ||
23 | +import org.thingsboard.server.dao.Dao; | ||
24 | +import org.thingsboard.server.dao.model.AlarmEntity; | ||
25 | + | ||
26 | +import java.util.List; | ||
27 | +import java.util.UUID; | ||
28 | + | ||
18 | /** | 29 | /** |
19 | * Created by ashvayka on 11.05.17. | 30 | * Created by ashvayka on 11.05.17. |
20 | */ | 31 | */ |
21 | -public interface AlarmDao { | 32 | +public interface AlarmDao extends Dao<AlarmEntity> { |
33 | + | ||
34 | + ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); | ||
35 | + | ||
36 | + ListenableFuture<Alarm> findAlarmByIdAsync(UUID key); | ||
37 | + | ||
38 | + AlarmEntity save(Alarm alarm); | ||
39 | + | ||
40 | + ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query); | ||
22 | } | 41 | } |
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.dao.alarm; | ||
17 | + | ||
18 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | ||
19 | +import com.datastax.driver.core.querybuilder.Select; | ||
20 | +import com.google.common.util.concurrent.AsyncFunction; | ||
21 | +import com.google.common.util.concurrent.Futures; | ||
22 | +import com.google.common.util.concurrent.ListenableFuture; | ||
23 | +import lombok.extern.slf4j.Slf4j; | ||
24 | +import org.springframework.beans.factory.annotation.Autowired; | ||
25 | +import org.springframework.stereotype.Component; | ||
26 | +import org.thingsboard.server.common.data.EntityType; | ||
27 | +import org.thingsboard.server.common.data.alarm.Alarm; | ||
28 | +import org.thingsboard.server.common.data.alarm.AlarmQuery; | ||
29 | +import org.thingsboard.server.common.data.id.EntityId; | ||
30 | +import org.thingsboard.server.common.data.id.TenantId; | ||
31 | +import org.thingsboard.server.common.data.relation.EntityRelation; | ||
32 | +import org.thingsboard.server.dao.AbstractModelDao; | ||
33 | +import org.thingsboard.server.dao.AbstractSearchTimeDao; | ||
34 | +import org.thingsboard.server.dao.model.AlarmEntity; | ||
35 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
36 | +import org.thingsboard.server.dao.relation.RelationDao; | ||
37 | + | ||
38 | +import java.util.ArrayList; | ||
39 | +import java.util.List; | ||
40 | +import java.util.UUID; | ||
41 | + | ||
42 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | ||
43 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; | ||
44 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
45 | + | ||
46 | +@Component | ||
47 | +@Slf4j | ||
48 | +public class AlarmDaoImpl extends AbstractModelDao<AlarmEntity> implements AlarmDao { | ||
49 | + | ||
50 | + @Autowired | ||
51 | + private RelationDao relationDao; | ||
52 | + | ||
53 | + @Override | ||
54 | + protected Class<AlarmEntity> getColumnFamilyClass() { | ||
55 | + return AlarmEntity.class; | ||
56 | + } | ||
57 | + | ||
58 | + @Override | ||
59 | + protected String getColumnFamilyName() { | ||
60 | + return ALARM_COLUMN_FAMILY_NAME; | ||
61 | + } | ||
62 | + | ||
63 | + protected boolean isDeleteOnSave() { | ||
64 | + return false; | ||
65 | + } | ||
66 | + | ||
67 | + @Override | ||
68 | + public AlarmEntity save(Alarm alarm) { | ||
69 | + log.debug("Save asset [{}] ", alarm); | ||
70 | + return save(new AlarmEntity(alarm)); | ||
71 | + } | ||
72 | + | ||
73 | + @Override | ||
74 | + public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { | ||
75 | + Select select = select().from(ALARM_COLUMN_FAMILY_NAME); | ||
76 | + Select.Where query = select.where(); | ||
77 | + query.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId())); | ||
78 | + query.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, originator.getId())); | ||
79 | + query.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, originator.getEntityType())); | ||
80 | + query.and(eq(ALARM_TYPE_PROPERTY, type)); | ||
81 | + query.limit(1); | ||
82 | + query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY)); | ||
83 | + return Futures.transform(findOneByStatementAsync(query), toDataFunction()); | ||
84 | + } | ||
85 | + | ||
86 | + @Override | ||
87 | + public ListenableFuture<Alarm> findAlarmByIdAsync(UUID key) { | ||
88 | + log.debug("Get alarm by id {}", key); | ||
89 | + Select.Where query = select().from(ALARM_BY_ID_VIEW_NAME).where(eq(ModelConstants.ID_PROPERTY, key)); | ||
90 | + query.limit(1); | ||
91 | + log.trace("Execute query {}", query); | ||
92 | + return Futures.transform(findOneByStatementAsync(query), toDataFunction()); | ||
93 | + } | ||
94 | + | ||
95 | + @Override | ||
96 | + public ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query) { | ||
97 | + log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink()); | ||
98 | + EntityId affectedEntity = query.getAffectedEntityId(); | ||
99 | + String relationType = query.getStatus() == null ? BaseAlarmService.ALARM_RELATION : BaseAlarmService.ALARM_RELATION_PREFIX + query.getStatus().name(); | ||
100 | + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(affectedEntity, relationType, EntityType.ALARM, query.getPageLink()); | ||
101 | + return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Alarm>>) input -> { | ||
102 | + List<ListenableFuture<Alarm>> alarmFutures = new ArrayList<>(input.size()); | ||
103 | + for (EntityRelation relation : input) { | ||
104 | + alarmFutures.add(findAlarmByIdAsync(relation.getTo().getId())); | ||
105 | + } | ||
106 | + return Futures.successfulAsList(alarmFutures); | ||
107 | + }); | ||
108 | + } | ||
109 | +} |
@@ -21,24 +21,18 @@ import org.thingsboard.server.common.data.alarm.AlarmId; | @@ -21,24 +21,18 @@ import org.thingsboard.server.common.data.alarm.AlarmId; | ||
21 | import org.thingsboard.server.common.data.alarm.AlarmQuery; | 21 | import org.thingsboard.server.common.data.alarm.AlarmQuery; |
22 | import org.thingsboard.server.common.data.page.TimePageData; | 22 | import org.thingsboard.server.common.data.page.TimePageData; |
23 | 23 | ||
24 | -import java.util.Optional; | ||
25 | - | ||
26 | /** | 24 | /** |
27 | * Created by ashvayka on 11.05.17. | 25 | * Created by ashvayka on 11.05.17. |
28 | */ | 26 | */ |
29 | public interface AlarmService { | 27 | public interface AlarmService { |
30 | 28 | ||
31 | - Alarm findAlarmById(AlarmId alarmId); | ||
32 | - | ||
33 | - ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId); | 29 | + Alarm createOrUpdateAlarm(Alarm alarm); |
34 | 30 | ||
35 | - Optional<Alarm> saveIfNotExists(Alarm alarm); | 31 | + ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTs); |
36 | 32 | ||
37 | - ListenableFuture<Boolean> updateAlarm(Alarm alarm); | 33 | + ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs); |
38 | 34 | ||
39 | - ListenableFuture<Boolean> ackAlarm(Alarm alarm); | ||
40 | - | ||
41 | - ListenableFuture<Boolean> clearAlarm(AlarmId alarmId); | 35 | + ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId); |
42 | 36 | ||
43 | ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query); | 37 | ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query); |
44 | 38 |
@@ -15,52 +15,277 @@ | @@ -15,52 +15,277 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.dao.alarm; | 16 | package org.thingsboard.server.dao.alarm; |
17 | 17 | ||
18 | + | ||
19 | +import com.google.common.base.Function; | ||
20 | +import com.google.common.util.concurrent.Futures; | ||
18 | import com.google.common.util.concurrent.ListenableFuture; | 21 | import com.google.common.util.concurrent.ListenableFuture; |
19 | import lombok.extern.slf4j.Slf4j; | 22 | import lombok.extern.slf4j.Slf4j; |
23 | +import org.springframework.beans.factory.annotation.Autowired; | ||
20 | import org.springframework.stereotype.Service; | 24 | import org.springframework.stereotype.Service; |
25 | +import org.springframework.util.StringUtils; | ||
21 | import org.thingsboard.server.common.data.alarm.Alarm; | 26 | import org.thingsboard.server.common.data.alarm.Alarm; |
22 | import org.thingsboard.server.common.data.alarm.AlarmId; | 27 | import org.thingsboard.server.common.data.alarm.AlarmId; |
23 | import org.thingsboard.server.common.data.alarm.AlarmQuery; | 28 | import org.thingsboard.server.common.data.alarm.AlarmQuery; |
29 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | ||
30 | +import org.thingsboard.server.common.data.id.EntityId; | ||
24 | import org.thingsboard.server.common.data.page.TimePageData; | 31 | import org.thingsboard.server.common.data.page.TimePageData; |
32 | +import org.thingsboard.server.common.data.relation.EntityRelation; | ||
33 | +import org.thingsboard.server.dao.entity.AbstractEntityService; | ||
34 | +import org.thingsboard.server.dao.entity.BaseEntityService; | ||
35 | +import org.thingsboard.server.dao.exception.DataValidationException; | ||
36 | +import org.thingsboard.server.dao.model.*; | ||
37 | +import org.thingsboard.server.dao.relation.EntityRelationsQuery; | ||
38 | +import org.thingsboard.server.dao.relation.EntitySearchDirection; | ||
39 | +import org.thingsboard.server.dao.relation.RelationService; | ||
40 | +import org.thingsboard.server.dao.relation.RelationsSearchParameters; | ||
41 | +import org.thingsboard.server.dao.service.DataValidator; | ||
42 | +import org.thingsboard.server.dao.tenant.TenantDao; | ||
43 | + | ||
44 | +import javax.annotation.Nullable; | ||
45 | +import javax.annotation.PostConstruct; | ||
46 | +import javax.annotation.PreDestroy; | ||
47 | +import java.util.List; | ||
48 | +import java.util.concurrent.ExecutionException; | ||
49 | +import java.util.concurrent.ExecutorService; | ||
50 | +import java.util.concurrent.Executors; | ||
51 | +import java.util.stream.Collectors; | ||
25 | 52 | ||
26 | -import java.util.Optional; | 53 | +import static org.thingsboard.server.dao.DaoUtil.*; |
54 | +import static org.thingsboard.server.dao.service.Validator.*; | ||
27 | 55 | ||
28 | @Service | 56 | @Service |
29 | @Slf4j | 57 | @Slf4j |
30 | -public class BaseAlarmService implements AlarmService { | 58 | +public class BaseAlarmService extends AbstractEntityService implements AlarmService { |
31 | 59 | ||
32 | - @Override | ||
33 | - public Alarm findAlarmById(AlarmId alarmId) { | ||
34 | - return null; | 60 | + public static final String ALARM_RELATION_PREFIX = "ALARM_"; |
61 | + public static final String ALARM_RELATION = "ALARM_ANY"; | ||
62 | + | ||
63 | + @Autowired | ||
64 | + private AlarmDao alarmDao; | ||
65 | + | ||
66 | + @Autowired | ||
67 | + private TenantDao tenantDao; | ||
68 | + | ||
69 | + @Autowired | ||
70 | + private RelationService relationService; | ||
71 | + | ||
72 | + protected ExecutorService readResultsProcessingExecutor; | ||
73 | + | ||
74 | + @PostConstruct | ||
75 | + public void startExecutor() { | ||
76 | + readResultsProcessingExecutor = Executors.newCachedThreadPool(); | ||
35 | } | 77 | } |
36 | 78 | ||
37 | - @Override | ||
38 | - public ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId) { | ||
39 | - return null; | 79 | + @PreDestroy |
80 | + public void stopExecutor() { | ||
81 | + if (readResultsProcessingExecutor != null) { | ||
82 | + readResultsProcessingExecutor.shutdownNow(); | ||
83 | + } | ||
40 | } | 84 | } |
41 | 85 | ||
42 | @Override | 86 | @Override |
43 | - public Optional<Alarm> saveIfNotExists(Alarm alarm) { | ||
44 | - return null; | 87 | + public Alarm createOrUpdateAlarm(Alarm alarm) { |
88 | + alarmDataValidator.validate(alarm); | ||
89 | + try { | ||
90 | + if (alarm.getStartTs() == 0L) { | ||
91 | + alarm.setStartTs(System.currentTimeMillis()); | ||
92 | + } | ||
93 | + if (alarm.getEndTs() == 0L) { | ||
94 | + alarm.setEndTs(alarm.getStartTs()); | ||
95 | + } | ||
96 | + if (alarm.getId() == null) { | ||
97 | + Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); | ||
98 | + if (existing == null || existing.getStatus().isCleared()) { | ||
99 | + return createAlarm(alarm); | ||
100 | + } else { | ||
101 | + return updateAlarm(existing, alarm); | ||
102 | + } | ||
103 | + } else { | ||
104 | + return updateAlarm(alarm).get(); | ||
105 | + } | ||
106 | + } catch (ExecutionException | InterruptedException e) { | ||
107 | + throw new RuntimeException(e); | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + private Alarm createAlarm(Alarm alarm) throws InterruptedException, ExecutionException { | ||
112 | + log.debug("New Alarm : {}", alarm); | ||
113 | + Alarm saved = getData(alarmDao.save(new AlarmEntity(alarm))); | ||
114 | + EntityRelationsQuery query = new EntityRelationsQuery(); | ||
115 | + query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); | ||
116 | + List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); | ||
117 | + for (EntityId parentId : parentEntities) { | ||
118 | + createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION)); | ||
119 | + createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name())); | ||
120 | + } | ||
121 | + createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION)); | ||
122 | + createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name())); | ||
123 | + return saved; | ||
124 | + } | ||
125 | + | ||
126 | + protected ListenableFuture<Alarm> updateAlarm(Alarm update) { | ||
127 | + alarmDataValidator.validate(update); | ||
128 | + return getAndUpdate(update.getId(), new Function<Alarm, Alarm>() { | ||
129 | + @Nullable | ||
130 | + @Override | ||
131 | + public Alarm apply(@Nullable Alarm alarm) { | ||
132 | + if (alarm == null) { | ||
133 | + return null; | ||
134 | + } else { | ||
135 | + return updateAlarm(alarm, update); | ||
136 | + } | ||
137 | + } | ||
138 | + }); | ||
139 | + } | ||
140 | + | ||
141 | + private Alarm updateAlarm(Alarm oldAlarm, Alarm newAlarm) { | ||
142 | + AlarmStatus oldStatus = oldAlarm.getStatus(); | ||
143 | + AlarmStatus newStatus = newAlarm.getStatus(); | ||
144 | + AlarmEntity result = alarmDao.save(new AlarmEntity(merge(oldAlarm, newAlarm))); | ||
145 | + if (oldStatus != newStatus) { | ||
146 | + updateRelations(oldAlarm, oldStatus, newStatus); | ||
147 | + } | ||
148 | + return result.toData(); | ||
45 | } | 149 | } |
46 | 150 | ||
47 | @Override | 151 | @Override |
48 | - public ListenableFuture<Boolean> updateAlarm(Alarm alarm) { | ||
49 | - return null; | 152 | + public ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTime) { |
153 | + return getAndUpdate(alarmId, new Function<Alarm, Boolean>() { | ||
154 | + @Nullable | ||
155 | + @Override | ||
156 | + public Boolean apply(@Nullable Alarm alarm) { | ||
157 | + if (alarm == null || alarm.getStatus().isAck()) { | ||
158 | + return false; | ||
159 | + } else { | ||
160 | + AlarmStatus oldStatus = alarm.getStatus(); | ||
161 | + AlarmStatus newStatus = oldStatus.isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK; | ||
162 | + alarm.setStatus(newStatus); | ||
163 | + alarm.setAckTs(ackTime); | ||
164 | + alarmDao.save(new AlarmEntity(alarm)); | ||
165 | + updateRelations(alarm, oldStatus, newStatus); | ||
166 | + return true; | ||
167 | + } | ||
168 | + } | ||
169 | + }); | ||
50 | } | 170 | } |
51 | 171 | ||
52 | @Override | 172 | @Override |
53 | - public ListenableFuture<Boolean> ackAlarm(Alarm alarm) { | ||
54 | - return null; | 173 | + public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTime) { |
174 | + return getAndUpdate(alarmId, new Function<Alarm, Boolean>() { | ||
175 | + @Nullable | ||
176 | + @Override | ||
177 | + public Boolean apply(@Nullable Alarm alarm) { | ||
178 | + if (alarm == null || alarm.getStatus().isCleared()) { | ||
179 | + return false; | ||
180 | + } else { | ||
181 | + AlarmStatus oldStatus = alarm.getStatus(); | ||
182 | + AlarmStatus newStatus = oldStatus.isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK; | ||
183 | + alarm.setStatus(newStatus); | ||
184 | + alarm.setClearTs(clearTime); | ||
185 | + alarmDao.save(new AlarmEntity(alarm)); | ||
186 | + updateRelations(alarm, oldStatus, newStatus); | ||
187 | + return true; | ||
188 | + } | ||
189 | + } | ||
190 | + }); | ||
55 | } | 191 | } |
56 | 192 | ||
57 | @Override | 193 | @Override |
58 | - public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId) { | ||
59 | - return null; | 194 | + public ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId) { |
195 | + log.trace("Executing findAlarmById [{}]", alarmId); | ||
196 | + validateId(alarmId, "Incorrect alarmId " + alarmId); | ||
197 | + return alarmDao.findAlarmByIdAsync(alarmId.getId()); | ||
60 | } | 198 | } |
61 | 199 | ||
62 | @Override | 200 | @Override |
63 | public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) { | 201 | public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) { |
64 | - return null; | 202 | + ListenableFuture<List<Alarm>> alarms = alarmDao.findAlarms(query); |
203 | + return Futures.transform(alarms, new Function<List<Alarm>, TimePageData<Alarm>>() { | ||
204 | + @Nullable | ||
205 | + @Override | ||
206 | + public TimePageData<Alarm> apply(@Nullable List<Alarm> alarms) { | ||
207 | + return new TimePageData<>(alarms, query.getPageLink()); | ||
208 | + } | ||
209 | + }); | ||
210 | + } | ||
211 | + | ||
212 | + private void deleteRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException { | ||
213 | + log.debug("Deleting Alarm relation: {}", alarmRelation); | ||
214 | + relationService.deleteRelation(alarmRelation).get(); | ||
215 | + } | ||
216 | + | ||
217 | + private void createRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException { | ||
218 | + log.debug("Creating Alarm relation: {}", alarmRelation); | ||
219 | + relationService.saveRelation(alarmRelation).get(); | ||
220 | + } | ||
221 | + | ||
222 | + private Alarm merge(Alarm existing, Alarm alarm) { | ||
223 | + if (alarm.getStartTs() > existing.getEndTs()) { | ||
224 | + existing.setEndTs(alarm.getStartTs()); | ||
225 | + } | ||
226 | + if (alarm.getEndTs() > existing.getEndTs()) { | ||
227 | + existing.setEndTs(alarm.getEndTs()); | ||
228 | + } | ||
229 | + if (alarm.getClearTs() > existing.getClearTs()) { | ||
230 | + existing.setClearTs(alarm.getClearTs()); | ||
231 | + } | ||
232 | + if (alarm.getAckTs() > existing.getAckTs()) { | ||
233 | + existing.setAckTs(alarm.getAckTs()); | ||
234 | + } | ||
235 | + existing.setStatus(alarm.getStatus()); | ||
236 | + existing.setSeverity(alarm.getSeverity()); | ||
237 | + existing.setDetails(alarm.getDetails()); | ||
238 | + return existing; | ||
239 | + } | ||
240 | + | ||
241 | + private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) { | ||
242 | + try { | ||
243 | + EntityRelationsQuery query = new EntityRelationsQuery(); | ||
244 | + query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); | ||
245 | + List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); | ||
246 | + for (EntityId parentId : parentEntities) { | ||
247 | + deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name())); | ||
248 | + createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name())); | ||
249 | + } | ||
250 | + deleteRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name())); | ||
251 | + createRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name())); | ||
252 | + } catch (ExecutionException | InterruptedException e) { | ||
253 | + log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus); | ||
254 | + throw new RuntimeException(e); | ||
255 | + } | ||
65 | } | 256 | } |
257 | + | ||
258 | + private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) { | ||
259 | + validateId(alarmId, "Alarm id should be specified!"); | ||
260 | + ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId()); | ||
261 | + return Futures.transform(entity, function, readResultsProcessingExecutor); | ||
262 | + } | ||
263 | + | ||
264 | + private DataValidator<Alarm> alarmDataValidator = | ||
265 | + new DataValidator<Alarm>() { | ||
266 | + | ||
267 | + @Override | ||
268 | + protected void validateDataImpl(Alarm alarm) { | ||
269 | + if (StringUtils.isEmpty(alarm.getType())) { | ||
270 | + throw new DataValidationException("Alarm type should be specified!"); | ||
271 | + } | ||
272 | + if (alarm.getOriginator() == null) { | ||
273 | + throw new DataValidationException("Alarm originator should be specified!"); | ||
274 | + } | ||
275 | + if (alarm.getSeverity() == null) { | ||
276 | + throw new DataValidationException("Alarm severity should be specified!"); | ||
277 | + } | ||
278 | + if (alarm.getStatus() == null) { | ||
279 | + throw new DataValidationException("Alarm status should be specified!"); | ||
280 | + } | ||
281 | + if (alarm.getTenantId() == null) { | ||
282 | + throw new DataValidationException("Alarm should be assigned to tenant!"); | ||
283 | + } else { | ||
284 | + TenantEntity tenant = tenantDao.findById(alarm.getTenantId().getId()); | ||
285 | + if (tenant == null) { | ||
286 | + throw new DataValidationException("Alarm is referencing to non-existent tenant!"); | ||
287 | + } | ||
288 | + } | ||
289 | + } | ||
290 | + }; | ||
66 | } | 291 | } |
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.dao.model; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import com.datastax.driver.mapping.annotations.*; | ||
20 | +import com.fasterxml.jackson.databind.JsonNode; | ||
21 | +import org.thingsboard.server.common.data.EntityType; | ||
22 | +import org.thingsboard.server.common.data.alarm.Alarm; | ||
23 | +import org.thingsboard.server.common.data.alarm.AlarmId; | ||
24 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | ||
25 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | ||
26 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | ||
27 | +import org.thingsboard.server.common.data.id.TenantId; | ||
28 | +import org.thingsboard.server.dao.model.type.AlarmSeverityCodec; | ||
29 | +import org.thingsboard.server.dao.model.type.AlarmStatusCodec; | ||
30 | +import org.thingsboard.server.dao.model.type.EntityTypeCodec; | ||
31 | +import org.thingsboard.server.dao.model.type.JsonCodec; | ||
32 | + | ||
33 | +import java.util.UUID; | ||
34 | + | ||
35 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
36 | + | ||
37 | +@Table(name = ALARM_COLUMN_FAMILY_NAME) | ||
38 | +public final class AlarmEntity implements BaseEntity<Alarm> { | ||
39 | + | ||
40 | + @Transient | ||
41 | + private static final long serialVersionUID = -1265181166886910152L; | ||
42 | + | ||
43 | + @ClusteringColumn(value = 1) | ||
44 | + @Column(name = ID_PROPERTY) | ||
45 | + private UUID id; | ||
46 | + | ||
47 | + @PartitionKey(value = 0) | ||
48 | + @Column(name = ALARM_TENANT_ID_PROPERTY) | ||
49 | + private UUID tenantId; | ||
50 | + | ||
51 | + @PartitionKey(value = 1) | ||
52 | + @Column(name = ALARM_ORIGINATOR_ID_PROPERTY) | ||
53 | + private UUID originatorId; | ||
54 | + | ||
55 | + @PartitionKey(value = 2) | ||
56 | + @Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY, codec = EntityTypeCodec.class) | ||
57 | + private EntityType originatorType; | ||
58 | + | ||
59 | + @ClusteringColumn(value = 0) | ||
60 | + @Column(name = ALARM_TYPE_PROPERTY) | ||
61 | + private String type; | ||
62 | + | ||
63 | + @Column(name = ALARM_SEVERITY_PROPERTY, codec = AlarmSeverityCodec.class) | ||
64 | + private AlarmSeverity severity; | ||
65 | + | ||
66 | + @Column(name = ALARM_STATUS_PROPERTY, codec = AlarmStatusCodec.class) | ||
67 | + private AlarmStatus status; | ||
68 | + | ||
69 | + @Column(name = ALARM_START_TS_PROPERTY) | ||
70 | + private Long startTs; | ||
71 | + | ||
72 | + @Column(name = ALARM_END_TS_PROPERTY) | ||
73 | + private Long endTs; | ||
74 | + | ||
75 | + @Column(name = ALARM_ACK_TS_PROPERTY) | ||
76 | + private Long ackTs; | ||
77 | + | ||
78 | + @Column(name = ALARM_CLEAR_TS_PROPERTY) | ||
79 | + private Long clearTs; | ||
80 | + | ||
81 | + @Column(name = ALARM_DETAILS_PROPERTY, codec = JsonCodec.class) | ||
82 | + private JsonNode details; | ||
83 | + | ||
84 | + @Column(name = ALARM_PROPAGATE_PROPERTY) | ||
85 | + private Boolean propagate; | ||
86 | + | ||
87 | + public AlarmEntity() { | ||
88 | + super(); | ||
89 | + } | ||
90 | + | ||
91 | + public AlarmEntity(Alarm alarm) { | ||
92 | + if (alarm.getId() != null) { | ||
93 | + this.id = alarm.getId().getId(); | ||
94 | + } | ||
95 | + if (alarm.getTenantId() != null) { | ||
96 | + this.tenantId = alarm.getTenantId().getId(); | ||
97 | + } | ||
98 | + this.type = alarm.getType(); | ||
99 | + this.originatorId = alarm.getOriginator().getId(); | ||
100 | + this.originatorType = alarm.getOriginator().getEntityType(); | ||
101 | + this.type = alarm.getType(); | ||
102 | + this.severity = alarm.getSeverity(); | ||
103 | + this.status = alarm.getStatus(); | ||
104 | + this.propagate = alarm.isPropagate(); | ||
105 | + this.startTs = alarm.getStartTs(); | ||
106 | + this.endTs = alarm.getEndTs(); | ||
107 | + this.ackTs = alarm.getAckTs(); | ||
108 | + this.clearTs = alarm.getClearTs(); | ||
109 | + this.details = alarm.getDetails(); | ||
110 | + } | ||
111 | + | ||
112 | + public UUID getId() { | ||
113 | + return id; | ||
114 | + } | ||
115 | + | ||
116 | + public void setId(UUID id) { | ||
117 | + this.id = id; | ||
118 | + } | ||
119 | + | ||
120 | + public UUID getTenantId() { | ||
121 | + return tenantId; | ||
122 | + } | ||
123 | + | ||
124 | + public void setTenantId(UUID tenantId) { | ||
125 | + this.tenantId = tenantId; | ||
126 | + } | ||
127 | + | ||
128 | + public UUID getOriginatorId() { | ||
129 | + return originatorId; | ||
130 | + } | ||
131 | + | ||
132 | + public void setOriginatorId(UUID originatorId) { | ||
133 | + this.originatorId = originatorId; | ||
134 | + } | ||
135 | + | ||
136 | + public EntityType getOriginatorType() { | ||
137 | + return originatorType; | ||
138 | + } | ||
139 | + | ||
140 | + public void setOriginatorType(EntityType originatorType) { | ||
141 | + this.originatorType = originatorType; | ||
142 | + } | ||
143 | + | ||
144 | + public String getType() { | ||
145 | + return type; | ||
146 | + } | ||
147 | + | ||
148 | + public void setType(String type) { | ||
149 | + this.type = type; | ||
150 | + } | ||
151 | + | ||
152 | + public AlarmSeverity getSeverity() { | ||
153 | + return severity; | ||
154 | + } | ||
155 | + | ||
156 | + public void setSeverity(AlarmSeverity severity) { | ||
157 | + this.severity = severity; | ||
158 | + } | ||
159 | + | ||
160 | + public AlarmStatus getStatus() { | ||
161 | + return status; | ||
162 | + } | ||
163 | + | ||
164 | + public void setStatus(AlarmStatus status) { | ||
165 | + this.status = status; | ||
166 | + } | ||
167 | + | ||
168 | + public Long getStartTs() { | ||
169 | + return startTs; | ||
170 | + } | ||
171 | + | ||
172 | + public void setStartTs(Long startTs) { | ||
173 | + this.startTs = startTs; | ||
174 | + } | ||
175 | + | ||
176 | + public Long getEndTs() { | ||
177 | + return endTs; | ||
178 | + } | ||
179 | + | ||
180 | + public void setEndTs(Long endTs) { | ||
181 | + this.endTs = endTs; | ||
182 | + } | ||
183 | + | ||
184 | + public Long getAckTs() { | ||
185 | + return ackTs; | ||
186 | + } | ||
187 | + | ||
188 | + public void setAckTs(Long ackTs) { | ||
189 | + this.ackTs = ackTs; | ||
190 | + } | ||
191 | + | ||
192 | + public Long getClearTs() { | ||
193 | + return clearTs; | ||
194 | + } | ||
195 | + | ||
196 | + public void setClearTs(Long clearTs) { | ||
197 | + this.clearTs = clearTs; | ||
198 | + } | ||
199 | + | ||
200 | + public JsonNode getDetails() { | ||
201 | + return details; | ||
202 | + } | ||
203 | + | ||
204 | + public void setDetails(JsonNode details) { | ||
205 | + this.details = details; | ||
206 | + } | ||
207 | + | ||
208 | + public Boolean getPropagate() { | ||
209 | + return propagate; | ||
210 | + } | ||
211 | + | ||
212 | + public void setPropagate(Boolean propagate) { | ||
213 | + this.propagate = propagate; | ||
214 | + } | ||
215 | + | ||
216 | + @Override | ||
217 | + public Alarm toData() { | ||
218 | + Alarm alarm = new Alarm(new AlarmId(id)); | ||
219 | + alarm.setCreatedTime(UUIDs.unixTimestamp(id)); | ||
220 | + if (tenantId != null) { | ||
221 | + alarm.setTenantId(new TenantId(tenantId)); | ||
222 | + } | ||
223 | + alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId)); | ||
224 | + alarm.setType(type); | ||
225 | + alarm.setSeverity(severity); | ||
226 | + alarm.setStatus(status); | ||
227 | + alarm.setPropagate(propagate); | ||
228 | + alarm.setStartTs(startTs); | ||
229 | + alarm.setEndTs(endTs); | ||
230 | + alarm.setAckTs(ackTs); | ||
231 | + alarm.setClearTs(clearTs); | ||
232 | + alarm.setDetails(details); | ||
233 | + return alarm; | ||
234 | + } | ||
235 | + | ||
236 | +} |
@@ -148,6 +148,25 @@ public class ModelConstants { | @@ -148,6 +148,25 @@ public class ModelConstants { | ||
148 | public static final String ASSET_TYPES_BY_TENANT_VIEW_NAME = "asset_types_by_tenant"; | 148 | public static final String ASSET_TYPES_BY_TENANT_VIEW_NAME = "asset_types_by_tenant"; |
149 | 149 | ||
150 | /** | 150 | /** |
151 | + * Cassandra alarm constants. | ||
152 | + */ | ||
153 | + public static final String ALARM_COLUMN_FAMILY_NAME = "alarm"; | ||
154 | + public static final String ALARM_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY; | ||
155 | + public static final String ALARM_TYPE_PROPERTY = "type"; | ||
156 | + public static final String ALARM_DETAILS_PROPERTY = "details"; | ||
157 | + public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id"; | ||
158 | + public static final String ALARM_ORIGINATOR_TYPE_PROPERTY = "originator_type"; | ||
159 | + public static final String ALARM_SEVERITY_PROPERTY = "severity"; | ||
160 | + public static final String ALARM_STATUS_PROPERTY = "status"; | ||
161 | + public static final String ALARM_START_TS_PROPERTY = "start_ts"; | ||
162 | + public static final String ALARM_END_TS_PROPERTY = "end_ts"; | ||
163 | + public static final String ALARM_ACK_TS_PROPERTY = "ack_ts"; | ||
164 | + public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts"; | ||
165 | + public static final String ALARM_PROPAGATE_PROPERTY = "propagate"; | ||
166 | + | ||
167 | + public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; | ||
168 | + | ||
169 | + /** | ||
151 | * Cassandra entity relation constants. | 170 | * Cassandra entity relation constants. |
152 | */ | 171 | */ |
153 | public static final String RELATION_COLUMN_FAMILY_NAME = "relation"; | 172 | public static final String RELATION_COLUMN_FAMILY_NAME = "relation"; |
@@ -157,6 +176,7 @@ public class ModelConstants { | @@ -157,6 +176,7 @@ public class ModelConstants { | ||
157 | public static final String RELATION_TO_TYPE_PROPERTY = "to_type"; | 176 | public static final String RELATION_TO_TYPE_PROPERTY = "to_type"; |
158 | public static final String RELATION_TYPE_PROPERTY = "relation_type"; | 177 | public static final String RELATION_TYPE_PROPERTY = "relation_type"; |
159 | 178 | ||
179 | + public static final String RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME = "relation_by_type_and_child_type"; | ||
160 | public static final String RELATION_REVERSE_VIEW_NAME = "reverse_relation"; | 180 | public static final String RELATION_REVERSE_VIEW_NAME = "reverse_relation"; |
161 | 181 | ||
162 | 182 |
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.dao.model.type; | ||
17 | + | ||
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | ||
19 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | ||
20 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | ||
21 | +import org.thingsboard.server.dao.alarm.AlarmService; | ||
22 | + | ||
23 | +public class AlarmSeverityCodec extends EnumNameCodec<AlarmSeverity> { | ||
24 | + | ||
25 | + public AlarmSeverityCodec() { | ||
26 | + super(AlarmSeverity.class); | ||
27 | + } | ||
28 | + | ||
29 | +} |
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.dao.model.type; | ||
17 | + | ||
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | ||
19 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | ||
20 | + | ||
21 | +public class AlarmStatusCodec extends EnumNameCodec<AlarmStatus> { | ||
22 | + | ||
23 | + public AlarmStatusCodec() { | ||
24 | + super(AlarmStatus.class); | ||
25 | + } | ||
26 | + | ||
27 | +} |
@@ -16,23 +16,32 @@ | @@ -16,23 +16,32 @@ | ||
16 | package org.thingsboard.server.dao.relation; | 16 | package org.thingsboard.server.dao.relation; |
17 | 17 | ||
18 | import com.datastax.driver.core.*; | 18 | import com.datastax.driver.core.*; |
19 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | ||
20 | +import com.datastax.driver.core.querybuilder.Select; | ||
19 | import com.fasterxml.jackson.databind.JsonNode; | 21 | import com.fasterxml.jackson.databind.JsonNode; |
20 | import com.google.common.base.Function; | 22 | import com.google.common.base.Function; |
21 | import com.google.common.util.concurrent.Futures; | 23 | import com.google.common.util.concurrent.Futures; |
22 | import com.google.common.util.concurrent.ListenableFuture; | 24 | import com.google.common.util.concurrent.ListenableFuture; |
23 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
24 | import org.springframework.stereotype.Component; | 26 | import org.springframework.stereotype.Component; |
27 | +import org.thingsboard.server.common.data.EntityType; | ||
25 | import org.thingsboard.server.common.data.id.EntityId; | 28 | import org.thingsboard.server.common.data.id.EntityId; |
26 | import org.thingsboard.server.common.data.id.EntityIdFactory; | 29 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
30 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
27 | import org.thingsboard.server.common.data.relation.EntityRelation; | 31 | import org.thingsboard.server.common.data.relation.EntityRelation; |
28 | import org.thingsboard.server.dao.AbstractAsyncDao; | 32 | import org.thingsboard.server.dao.AbstractAsyncDao; |
33 | +import org.thingsboard.server.dao.AbstractSearchTimeDao; | ||
29 | import org.thingsboard.server.dao.model.ModelConstants; | 34 | import org.thingsboard.server.dao.model.ModelConstants; |
30 | 35 | ||
31 | import javax.annotation.Nullable; | 36 | import javax.annotation.Nullable; |
32 | import javax.annotation.PostConstruct; | 37 | import javax.annotation.PostConstruct; |
33 | import java.util.ArrayList; | 38 | import java.util.ArrayList; |
39 | +import java.util.Arrays; | ||
34 | import java.util.List; | 40 | import java.util.List; |
35 | 41 | ||
42 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | ||
43 | +import static org.thingsboard.server.dao.model.ModelConstants.RELATION_COLUMN_FAMILY_NAME; | ||
44 | + | ||
36 | /** | 45 | /** |
37 | * Created by ashvayka on 25.04.17. | 46 | * Created by ashvayka on 25.04.17. |
38 | */ | 47 | */ |
@@ -145,6 +154,18 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { | @@ -145,6 +154,18 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { | ||
145 | return getBooleanListenableFuture(future); | 154 | return getBooleanListenableFuture(future); |
146 | } | 155 | } |
147 | 156 | ||
157 | + @Override | ||
158 | + public ListenableFuture<List<EntityRelation>> findRelations(EntityId from, String relationType, EntityType childType, TimePageLink pageLink) { | ||
159 | + Select.Where query = AbstractSearchTimeDao.buildQuery(ModelConstants.RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME, | ||
160 | + Arrays.asList(eq(ModelConstants.RELATION_FROM_ID_PROPERTY, from.getId()), | ||
161 | + eq(ModelConstants.RELATION_FROM_TYPE_PROPERTY, from.getEntityType().name()), | ||
162 | + eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType), | ||
163 | + eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())), | ||
164 | + Arrays.asList(QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY), QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)), | ||
165 | + pageLink, ModelConstants.RELATION_TO_ID_PROPERTY); | ||
166 | + return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs)); | ||
167 | + } | ||
168 | + | ||
148 | private PreparedStatement getSaveStmt() { | 169 | private PreparedStatement getSaveStmt() { |
149 | if (saveStmt == null) { | 170 | if (saveStmt == null) { |
150 | saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + | 171 | saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + |
@@ -235,31 +256,13 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { | @@ -235,31 +256,13 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { | ||
235 | return checkRelationStmt; | 256 | return checkRelationStmt; |
236 | } | 257 | } |
237 | 258 | ||
238 | - private EntityRelation getEntityRelation(Row row) { | ||
239 | - EntityRelation relation = new EntityRelation(); | ||
240 | - relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY)); | ||
241 | - relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class)); | ||
242 | - relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY)); | ||
243 | - relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY)); | ||
244 | - return relation; | ||
245 | - } | ||
246 | - | ||
247 | private EntityId toEntity(Row row, String uuidColumn, String typeColumn) { | 259 | private EntityId toEntity(Row row, String uuidColumn, String typeColumn) { |
248 | return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn)); | 260 | return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn)); |
249 | } | 261 | } |
250 | 262 | ||
251 | private ListenableFuture<List<EntityRelation>> executeAsyncRead(EntityId from, BoundStatement stmt) { | 263 | private ListenableFuture<List<EntityRelation>> executeAsyncRead(EntityId from, BoundStatement stmt) { |
252 | log.debug("Generated query [{}] for entity {}", stmt, from); | 264 | log.debug("Generated query [{}] for entity {}", stmt, from); |
253 | - return getFuture(executeAsyncRead(stmt), rs -> { | ||
254 | - List<Row> rows = rs.all(); | ||
255 | - List<EntityRelation> entries = new ArrayList<>(rows.size()); | ||
256 | - if (!rows.isEmpty()) { | ||
257 | - rows.forEach(row -> { | ||
258 | - entries.add(getEntityRelation(row)); | ||
259 | - }); | ||
260 | - } | ||
261 | - return entries; | ||
262 | - }); | 265 | + return getFuture(executeAsyncRead(stmt), rs -> getEntityRelations(rs)); |
263 | } | 266 | } |
264 | 267 | ||
265 | private ListenableFuture<Boolean> getBooleanListenableFuture(ResultSetFuture rsFuture) { | 268 | private ListenableFuture<Boolean> getBooleanListenableFuture(ResultSetFuture rsFuture) { |
@@ -276,4 +279,24 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { | @@ -276,4 +279,24 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { | ||
276 | }, readResultsProcessingExecutor); | 279 | }, readResultsProcessingExecutor); |
277 | } | 280 | } |
278 | 281 | ||
282 | + private List<EntityRelation> getEntityRelations(ResultSet rs) { | ||
283 | + List<Row> rows = rs.all(); | ||
284 | + List<EntityRelation> entries = new ArrayList<>(rows.size()); | ||
285 | + if (!rows.isEmpty()) { | ||
286 | + rows.forEach(row -> { | ||
287 | + entries.add(getEntityRelation(row)); | ||
288 | + }); | ||
289 | + } | ||
290 | + return entries; | ||
291 | + } | ||
292 | + | ||
293 | + private EntityRelation getEntityRelation(Row row) { | ||
294 | + EntityRelation relation = new EntityRelation(); | ||
295 | + relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY)); | ||
296 | + relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class)); | ||
297 | + relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY)); | ||
298 | + relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY)); | ||
299 | + return relation; | ||
300 | + } | ||
301 | + | ||
279 | } | 302 | } |
@@ -171,8 +171,7 @@ public class BaseRelationService implements RelationService { | @@ -171,8 +171,7 @@ public class BaseRelationService implements RelationService { | ||
171 | RelationsSearchParameters params = query.getParameters(); | 171 | RelationsSearchParameters params = query.getParameters(); |
172 | final List<EntityTypeFilter> filters = query.getFilters(); | 172 | final List<EntityTypeFilter> filters = query.getFilters(); |
173 | if (filters == null || filters.isEmpty()) { | 173 | if (filters == null || filters.isEmpty()) { |
174 | - log.warn("Failed to query relations. Filters are not set [{}]", query); | ||
175 | - throw new RuntimeException("Filters are not set!"); | 174 | + log.debug("Filters are not set [{}]", query); |
176 | } | 175 | } |
177 | 176 | ||
178 | int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE; | 177 | int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE; |
@@ -182,10 +181,14 @@ public class BaseRelationService implements RelationService { | @@ -182,10 +181,14 @@ public class BaseRelationService implements RelationService { | ||
182 | return Futures.transform(relationSet, (Function<Set<EntityRelation>, List<EntityRelation>>) input -> { | 181 | return Futures.transform(relationSet, (Function<Set<EntityRelation>, List<EntityRelation>>) input -> { |
183 | List<EntityRelation> relations = new ArrayList<>(); | 182 | List<EntityRelation> relations = new ArrayList<>(); |
184 | for (EntityRelation relation : input) { | 183 | for (EntityRelation relation : input) { |
185 | - for (EntityTypeFilter filter : filters) { | ||
186 | - if (match(filter, relation, params.getDirection())) { | ||
187 | - relations.add(relation); | ||
188 | - break; | 184 | + if (filters == null || filters.isEmpty()) { |
185 | + relations.add(relation); | ||
186 | + } else { | ||
187 | + for (EntityTypeFilter filter : filters) { | ||
188 | + if (match(filter, relation, params.getDirection())) { | ||
189 | + relations.add(relation); | ||
190 | + break; | ||
191 | + } | ||
189 | } | 192 | } |
190 | } | 193 | } |
191 | } | 194 | } |
@@ -254,7 +257,8 @@ public class BaseRelationService implements RelationService { | @@ -254,7 +257,8 @@ public class BaseRelationService implements RelationService { | ||
254 | } | 257 | } |
255 | } | 258 | } |
256 | 259 | ||
257 | - private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction, int lvl, final ConcurrentHashMap<EntityId, Boolean> uniqueMap) throws Exception { | 260 | + private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction, int lvl, |
261 | + final ConcurrentHashMap<EntityId, Boolean> uniqueMap) throws Exception { | ||
258 | if (lvl == 0) { | 262 | if (lvl == 0) { |
259 | return Futures.immediateFuture(Collections.emptySet()); | 263 | return Futures.immediateFuture(Collections.emptySet()); |
260 | } | 264 | } |
@@ -16,7 +16,9 @@ | @@ -16,7 +16,9 @@ | ||
16 | package org.thingsboard.server.dao.relation; | 16 | package org.thingsboard.server.dao.relation; |
17 | 17 | ||
18 | import com.google.common.util.concurrent.ListenableFuture; | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | +import org.thingsboard.server.common.data.EntityType; | ||
19 | import org.thingsboard.server.common.data.id.EntityId; | 20 | import org.thingsboard.server.common.data.id.EntityId; |
21 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
20 | import org.thingsboard.server.common.data.relation.EntityRelation; | 22 | import org.thingsboard.server.common.data.relation.EntityRelation; |
21 | 23 | ||
22 | import java.util.List; | 24 | import java.util.List; |
@@ -44,4 +46,6 @@ public interface RelationDao { | @@ -44,4 +46,6 @@ public interface RelationDao { | ||
44 | 46 | ||
45 | ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity); | 47 | ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity); |
46 | 48 | ||
49 | + ListenableFuture<List<EntityRelation>> findRelations(EntityId from, String relationType, EntityType toType, TimePageLink pageLink); | ||
50 | + | ||
47 | } | 51 | } |
@@ -49,4 +49,7 @@ public interface RelationService { | @@ -49,4 +49,7 @@ public interface RelationService { | ||
49 | 49 | ||
50 | ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); | 50 | ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); |
51 | 51 | ||
52 | +// TODO: This method may be useful for some validations in the future | ||
53 | +// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); | ||
54 | + | ||
52 | } | 55 | } |
@@ -277,6 +277,31 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_types_by_tenant AS | @@ -277,6 +277,31 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_types_by_tenant AS | ||
277 | PRIMARY KEY ( (type, tenant_id), id, customer_id) | 277 | PRIMARY KEY ( (type, tenant_id), id, customer_id) |
278 | WITH CLUSTERING ORDER BY ( id ASC, customer_id DESC); | 278 | WITH CLUSTERING ORDER BY ( id ASC, customer_id DESC); |
279 | 279 | ||
280 | +CREATE TABLE IF NOT EXISTS thingsboard.alarm ( | ||
281 | + id timeuuid, | ||
282 | + tenant_id timeuuid, | ||
283 | + type text, | ||
284 | + originator_id timeuuid, | ||
285 | + originator_type text, | ||
286 | + severity text, | ||
287 | + status text, | ||
288 | + start_ts bigint, | ||
289 | + end_ts bigint, | ||
290 | + ack_ts bigint, | ||
291 | + clear_ts bigint, | ||
292 | + details text, | ||
293 | + propagate boolean, | ||
294 | + PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id) | ||
295 | +) WITH CLUSTERING ORDER BY ( type ASC, id DESC); | ||
296 | + | ||
297 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.alarm_by_id AS | ||
298 | + SELECT * | ||
299 | + from thingsboard.alarm | ||
300 | + WHERE tenant_id IS NOT NULL AND originator_id IS NOT NULL AND originator_type IS NOT NULL AND type IS NOT NULL | ||
301 | + AND type IS NOT NULL AND id IS NOT NULL | ||
302 | + PRIMARY KEY (id, tenant_id, originator_id, originator_type, type) | ||
303 | + WITH CLUSTERING ORDER BY ( tenant_id ASC, originator_id ASC, originator_type ASC, type ASC); | ||
304 | + | ||
280 | CREATE TABLE IF NOT EXISTS thingsboard.relation ( | 305 | CREATE TABLE IF NOT EXISTS thingsboard.relation ( |
281 | from_id timeuuid, | 306 | from_id timeuuid, |
282 | from_type text, | 307 | from_type text, |
@@ -285,7 +310,14 @@ CREATE TABLE IF NOT EXISTS thingsboard.relation ( | @@ -285,7 +310,14 @@ CREATE TABLE IF NOT EXISTS thingsboard.relation ( | ||
285 | relation_type text, | 310 | relation_type text, |
286 | additional_info text, | 311 | additional_info text, |
287 | PRIMARY KEY ((from_id, from_type), relation_type, to_id, to_type) | 312 | PRIMARY KEY ((from_id, from_type), relation_type, to_id, to_type) |
288 | -); | 313 | +) WITH CLUSTERING ORDER BY ( relation_type ASC, to_id ASC, to_type ASC); |
314 | + | ||
315 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.relation_by_type_and_child_type AS | ||
316 | + SELECT * | ||
317 | + from thingsboard.relation | ||
318 | + 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 | ||
319 | + PRIMARY KEY ((from_id, from_type), relation_type, to_type, to_id) | ||
320 | + WITH CLUSTERING ORDER BY ( relation_type ASC, to_type ASC, to_id DESC); | ||
289 | 321 | ||
290 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS | 322 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS |
291 | SELECT * | 323 | SELECT * |
@@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.plugin.ComponentScope; | @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.plugin.ComponentScope; | ||
40 | import org.thingsboard.server.common.data.plugin.ComponentType; | 40 | import org.thingsboard.server.common.data.plugin.ComponentType; |
41 | import org.thingsboard.server.common.data.plugin.PluginMetaData; | 41 | import org.thingsboard.server.common.data.plugin.PluginMetaData; |
42 | import org.thingsboard.server.common.data.rule.RuleMetaData; | 42 | import org.thingsboard.server.common.data.rule.RuleMetaData; |
43 | +import org.thingsboard.server.dao.alarm.AlarmService; | ||
43 | import org.thingsboard.server.dao.asset.AssetService; | 44 | import org.thingsboard.server.dao.asset.AssetService; |
44 | import org.thingsboard.server.dao.component.ComponentDescriptorService; | 45 | import org.thingsboard.server.dao.component.ComponentDescriptorService; |
45 | import org.thingsboard.server.dao.customer.CustomerService; | 46 | import org.thingsboard.server.dao.customer.CustomerService; |
@@ -119,6 +120,9 @@ public abstract class AbstractServiceTest { | @@ -119,6 +120,9 @@ public abstract class AbstractServiceTest { | ||
119 | protected RelationService relationService; | 120 | protected RelationService relationService; |
120 | 121 | ||
121 | @Autowired | 122 | @Autowired |
123 | + protected AlarmService alarmService; | ||
124 | + | ||
125 | + @Autowired | ||
122 | private ComponentDescriptorService componentDescriptorService; | 126 | private ComponentDescriptorService componentDescriptorService; |
123 | 127 | ||
124 | class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> { | 128 | class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> { |
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.dao.service; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import org.junit.After; | ||
20 | +import org.junit.Assert; | ||
21 | +import org.junit.Before; | ||
22 | +import org.junit.Test; | ||
23 | +import org.thingsboard.server.common.data.EntityType; | ||
24 | +import org.thingsboard.server.common.data.Tenant; | ||
25 | +import org.thingsboard.server.common.data.alarm.Alarm; | ||
26 | +import org.thingsboard.server.common.data.alarm.AlarmQuery; | ||
27 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | ||
28 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | ||
29 | +import org.thingsboard.server.common.data.id.AssetId; | ||
30 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
31 | +import org.thingsboard.server.common.data.id.TenantId; | ||
32 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
33 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
34 | +import org.thingsboard.server.common.data.relation.EntityRelation; | ||
35 | +import org.thingsboard.server.dao.exception.DataValidationException; | ||
36 | +import org.thingsboard.server.dao.relation.EntityRelationsQuery; | ||
37 | +import org.thingsboard.server.dao.relation.EntitySearchDirection; | ||
38 | +import org.thingsboard.server.dao.relation.EntityTypeFilter; | ||
39 | +import org.thingsboard.server.dao.relation.RelationsSearchParameters; | ||
40 | + | ||
41 | +import java.util.Collections; | ||
42 | +import java.util.List; | ||
43 | +import java.util.concurrent.ExecutionException; | ||
44 | + | ||
45 | +public class AlarmServiceTest extends AbstractServiceTest { | ||
46 | + | ||
47 | + public static final String TEST_ALARM = "TEST_ALARM"; | ||
48 | + private TenantId tenantId; | ||
49 | + | ||
50 | + @Before | ||
51 | + public void before() { | ||
52 | + Tenant tenant = new Tenant(); | ||
53 | + tenant.setTitle("My tenant"); | ||
54 | + Tenant savedTenant = tenantService.saveTenant(tenant); | ||
55 | + Assert.assertNotNull(savedTenant); | ||
56 | + tenantId = savedTenant.getId(); | ||
57 | + } | ||
58 | + | ||
59 | + @After | ||
60 | + public void after() { | ||
61 | + tenantService.deleteTenant(tenantId); | ||
62 | + } | ||
63 | + | ||
64 | + | ||
65 | + @Test | ||
66 | + public void testSaveAndFetchAlarm() throws ExecutionException, InterruptedException { | ||
67 | + AssetId parentId = new AssetId(UUIDs.timeBased()); | ||
68 | + AssetId childId = new AssetId(UUIDs.timeBased()); | ||
69 | + | ||
70 | + EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); | ||
71 | + | ||
72 | + Assert.assertTrue(relationService.saveRelation(relation).get()); | ||
73 | + | ||
74 | + long ts = System.currentTimeMillis(); | ||
75 | + Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId) | ||
76 | + .type(TEST_ALARM) | ||
77 | + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) | ||
78 | + .startTs(ts).build(); | ||
79 | + | ||
80 | + Alarm created = alarmService.createOrUpdateAlarm(alarm); | ||
81 | + | ||
82 | + Assert.assertNotNull(created); | ||
83 | + Assert.assertNotNull(created.getId()); | ||
84 | + Assert.assertNotNull(created.getOriginator()); | ||
85 | + Assert.assertNotNull(created.getSeverity()); | ||
86 | + Assert.assertNotNull(created.getStatus()); | ||
87 | + | ||
88 | + Assert.assertEquals(tenantId, created.getTenantId()); | ||
89 | + Assert.assertEquals(childId, created.getOriginator()); | ||
90 | + Assert.assertEquals(TEST_ALARM, created.getType()); | ||
91 | + Assert.assertEquals(AlarmSeverity.CRITICAL, created.getSeverity()); | ||
92 | + Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, created.getStatus()); | ||
93 | + Assert.assertEquals(ts, created.getStartTs()); | ||
94 | + Assert.assertEquals(ts, created.getEndTs()); | ||
95 | + Assert.assertEquals(0L, created.getAckTs()); | ||
96 | + Assert.assertEquals(0L, created.getClearTs()); | ||
97 | + | ||
98 | + Alarm fetched = alarmService.findAlarmByIdAsync(created.getId()).get(); | ||
99 | + Assert.assertEquals(created, fetched); | ||
100 | + } | ||
101 | + | ||
102 | + @Test | ||
103 | + public void testFindAlarm() throws ExecutionException, InterruptedException { | ||
104 | + AssetId parentId = new AssetId(UUIDs.timeBased()); | ||
105 | + AssetId childId = new AssetId(UUIDs.timeBased()); | ||
106 | + | ||
107 | + EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); | ||
108 | + | ||
109 | + Assert.assertTrue(relationService.saveRelation(relation).get()); | ||
110 | + | ||
111 | + long ts = System.currentTimeMillis(); | ||
112 | + Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId) | ||
113 | + .type(TEST_ALARM) | ||
114 | + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) | ||
115 | + .startTs(ts).build(); | ||
116 | + | ||
117 | + Alarm created = alarmService.createOrUpdateAlarm(alarm); | ||
118 | + | ||
119 | + // Check child relation | ||
120 | + TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder() | ||
121 | + .affectedEntityId(childId) | ||
122 | + .status(AlarmStatus.ACTIVE_UNACK).pageLink( | ||
123 | + new TimePageLink(1, 0L, System.currentTimeMillis(), false) | ||
124 | + ).build()).get(); | ||
125 | + Assert.assertNotNull(alarms.getData()); | ||
126 | + Assert.assertEquals(1, alarms.getData().size()); | ||
127 | + Assert.assertEquals(created, alarms.getData().get(0)); | ||
128 | + | ||
129 | + // Check parent relation | ||
130 | + alarms = alarmService.findAlarms(AlarmQuery.builder() | ||
131 | + .affectedEntityId(parentId) | ||
132 | + .status(AlarmStatus.ACTIVE_UNACK).pageLink( | ||
133 | + new TimePageLink(1, 0L, System.currentTimeMillis(), false) | ||
134 | + ).build()).get(); | ||
135 | + Assert.assertNotNull(alarms.getData()); | ||
136 | + Assert.assertEquals(1, alarms.getData().size()); | ||
137 | + Assert.assertEquals(created, alarms.getData().get(0)); | ||
138 | + | ||
139 | + alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get(); | ||
140 | + created = alarmService.findAlarmByIdAsync(created.getId()).get(); | ||
141 | + | ||
142 | + alarms = alarmService.findAlarms(AlarmQuery.builder() | ||
143 | + .affectedEntityId(childId) | ||
144 | + .status(AlarmStatus.ACTIVE_ACK).pageLink( | ||
145 | + new TimePageLink(1, 0L, System.currentTimeMillis(), false) | ||
146 | + ).build()).get(); | ||
147 | + Assert.assertNotNull(alarms.getData()); | ||
148 | + Assert.assertEquals(1, alarms.getData().size()); | ||
149 | + Assert.assertEquals(created, alarms.getData().get(0)); | ||
150 | + | ||
151 | + // Check not existing relation | ||
152 | + alarms = alarmService.findAlarms(AlarmQuery.builder() | ||
153 | + .affectedEntityId(childId) | ||
154 | + .status(AlarmStatus.ACTIVE_UNACK).pageLink( | ||
155 | + new TimePageLink(1, 0L, System.currentTimeMillis(), false) | ||
156 | + ).build()).get(); | ||
157 | + Assert.assertNotNull(alarms.getData()); | ||
158 | + Assert.assertEquals(0, alarms.getData().size()); | ||
159 | + | ||
160 | + alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get(); | ||
161 | + created = alarmService.findAlarmByIdAsync(created.getId()).get(); | ||
162 | + | ||
163 | + alarms = alarmService.findAlarms(AlarmQuery.builder() | ||
164 | + .affectedEntityId(childId) | ||
165 | + .status(AlarmStatus.CLEARED_ACK).pageLink( | ||
166 | + new TimePageLink(1, 0L, System.currentTimeMillis(), false) | ||
167 | + ).build()).get(); | ||
168 | + Assert.assertNotNull(alarms.getData()); | ||
169 | + Assert.assertEquals(1, alarms.getData().size()); | ||
170 | + Assert.assertEquals(created, alarms.getData().get(0)); | ||
171 | + } | ||
172 | +} |
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.dao.service; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import com.google.common.util.concurrent.ListenableFuture; | ||
20 | +import org.junit.After; | ||
21 | +import org.junit.Assert; | ||
22 | +import org.junit.Before; | ||
23 | +import org.junit.Test; | ||
24 | +import org.thingsboard.server.common.data.EntityType; | ||
25 | +import org.thingsboard.server.common.data.asset.Asset; | ||
26 | +import org.thingsboard.server.common.data.id.AssetId; | ||
27 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
28 | +import org.thingsboard.server.common.data.relation.EntityRelation; | ||
29 | +import org.thingsboard.server.dao.exception.DataValidationException; | ||
30 | +import org.thingsboard.server.dao.relation.EntityRelationsQuery; | ||
31 | +import org.thingsboard.server.dao.relation.EntitySearchDirection; | ||
32 | +import org.thingsboard.server.dao.relation.EntityTypeFilter; | ||
33 | +import org.thingsboard.server.dao.relation.RelationsSearchParameters; | ||
34 | + | ||
35 | +import java.util.Collections; | ||
36 | +import java.util.List; | ||
37 | +import java.util.concurrent.ExecutionException; | ||
38 | + | ||
39 | +public class RelationServiceTest extends AbstractServiceTest { | ||
40 | + | ||
41 | + @Before | ||
42 | + public void before() { | ||
43 | + } | ||
44 | + | ||
45 | + @After | ||
46 | + public void after() { | ||
47 | + } | ||
48 | + | ||
49 | + @Test | ||
50 | + public void testSaveRelation() throws ExecutionException, InterruptedException { | ||
51 | + AssetId parentId = new AssetId(UUIDs.timeBased()); | ||
52 | + AssetId childId = new AssetId(UUIDs.timeBased()); | ||
53 | + | ||
54 | + EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); | ||
55 | + | ||
56 | + Assert.assertTrue(saveRelation(relation)); | ||
57 | + | ||
58 | + Assert.assertTrue(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get()); | ||
59 | + | ||
60 | + Assert.assertFalse(relationService.checkRelation(parentId, childId, "NOT_EXISTING_TYPE").get()); | ||
61 | + | ||
62 | + Assert.assertFalse(relationService.checkRelation(childId, parentId, EntityRelation.CONTAINS_TYPE).get()); | ||
63 | + | ||
64 | + Assert.assertFalse(relationService.checkRelation(childId, parentId, "NOT_EXISTING_TYPE").get()); | ||
65 | + } | ||
66 | + | ||
67 | + @Test | ||
68 | + public void testDeleteRelation() throws ExecutionException, InterruptedException { | ||
69 | + AssetId parentId = new AssetId(UUIDs.timeBased()); | ||
70 | + AssetId childId = new AssetId(UUIDs.timeBased()); | ||
71 | + AssetId subChildId = new AssetId(UUIDs.timeBased()); | ||
72 | + | ||
73 | + EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); | ||
74 | + EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); | ||
75 | + | ||
76 | + saveRelation(relationA); | ||
77 | + saveRelation(relationB); | ||
78 | + | ||
79 | + Assert.assertTrue(relationService.deleteRelation(relationA).get()); | ||
80 | + | ||
81 | + Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get()); | ||
82 | + | ||
83 | + Assert.assertTrue(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get()); | ||
84 | + | ||
85 | + Assert.assertTrue(relationService.deleteRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get()); | ||
86 | + } | ||
87 | + | ||
88 | + @Test | ||
89 | + public void testDeleteEntityRelations() throws ExecutionException, InterruptedException { | ||
90 | + AssetId parentId = new AssetId(UUIDs.timeBased()); | ||
91 | + AssetId childId = new AssetId(UUIDs.timeBased()); | ||
92 | + AssetId subChildId = new AssetId(UUIDs.timeBased()); | ||
93 | + | ||
94 | + EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); | ||
95 | + EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); | ||
96 | + | ||
97 | + saveRelation(relationA); | ||
98 | + saveRelation(relationB); | ||
99 | + | ||
100 | + Assert.assertTrue(relationService.deleteEntityRelations(childId).get()); | ||
101 | + | ||
102 | + Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get()); | ||
103 | + | ||
104 | + Assert.assertFalse(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get()); | ||
105 | + } | ||
106 | + | ||
107 | + @Test | ||
108 | + public void testFindFrom() throws ExecutionException, InterruptedException { | ||
109 | + AssetId parentA = new AssetId(UUIDs.timeBased()); | ||
110 | + AssetId parentB = new AssetId(UUIDs.timeBased()); | ||
111 | + AssetId childA = new AssetId(UUIDs.timeBased()); | ||
112 | + AssetId childB = new AssetId(UUIDs.timeBased()); | ||
113 | + | ||
114 | + EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE); | ||
115 | + EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE); | ||
116 | + | ||
117 | + EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE); | ||
118 | + EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE); | ||
119 | + | ||
120 | + saveRelation(relationA1); | ||
121 | + saveRelation(relationA2); | ||
122 | + | ||
123 | + saveRelation(relationB1); | ||
124 | + saveRelation(relationB2); | ||
125 | + | ||
126 | + List<EntityRelation> relations = relationService.findByFrom(parentA).get(); | ||
127 | + Assert.assertEquals(2, relations.size()); | ||
128 | + for (EntityRelation relation : relations) { | ||
129 | + Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType()); | ||
130 | + Assert.assertEquals(parentA, relation.getFrom()); | ||
131 | + Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo())); | ||
132 | + } | ||
133 | + | ||
134 | + relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE).get(); | ||
135 | + Assert.assertEquals(2, relations.size()); | ||
136 | + | ||
137 | + relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE).get(); | ||
138 | + Assert.assertEquals(0, relations.size()); | ||
139 | + | ||
140 | + relations = relationService.findByFrom(parentB).get(); | ||
141 | + Assert.assertEquals(2, relations.size()); | ||
142 | + for (EntityRelation relation : relations) { | ||
143 | + Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType()); | ||
144 | + Assert.assertEquals(parentB, relation.getFrom()); | ||
145 | + Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo())); | ||
146 | + } | ||
147 | + | ||
148 | + relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get(); | ||
149 | + Assert.assertEquals(0, relations.size()); | ||
150 | + | ||
151 | + relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get(); | ||
152 | + Assert.assertEquals(0, relations.size()); | ||
153 | + } | ||
154 | + | ||
155 | + private Boolean saveRelation(EntityRelation relationA1) throws ExecutionException, InterruptedException { | ||
156 | + return relationService.saveRelation(relationA1).get(); | ||
157 | + } | ||
158 | + | ||
159 | + @Test | ||
160 | + public void testFindTo() throws ExecutionException, InterruptedException { | ||
161 | + AssetId parentA = new AssetId(UUIDs.timeBased()); | ||
162 | + AssetId parentB = new AssetId(UUIDs.timeBased()); | ||
163 | + AssetId childA = new AssetId(UUIDs.timeBased()); | ||
164 | + AssetId childB = new AssetId(UUIDs.timeBased()); | ||
165 | + | ||
166 | + EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE); | ||
167 | + EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE); | ||
168 | + | ||
169 | + EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE); | ||
170 | + EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE); | ||
171 | + | ||
172 | + saveRelation(relationA1); | ||
173 | + saveRelation(relationA2); | ||
174 | + | ||
175 | + saveRelation(relationB1); | ||
176 | + saveRelation(relationB2); | ||
177 | + | ||
178 | + // Data propagation to views is async | ||
179 | + Thread.sleep(3000); | ||
180 | + | ||
181 | + List<EntityRelation> relations = relationService.findByTo(childA).get(); | ||
182 | + Assert.assertEquals(2, relations.size()); | ||
183 | + for (EntityRelation relation : relations) { | ||
184 | + Assert.assertEquals(childA, relation.getTo()); | ||
185 | + Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom())); | ||
186 | + } | ||
187 | + | ||
188 | + relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE).get(); | ||
189 | + Assert.assertEquals(1, relations.size()); | ||
190 | + | ||
191 | + relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE).get(); | ||
192 | + Assert.assertEquals(1, relations.size()); | ||
193 | + | ||
194 | + relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE).get(); | ||
195 | + Assert.assertEquals(0, relations.size()); | ||
196 | + | ||
197 | + relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE).get(); | ||
198 | + Assert.assertEquals(0, relations.size()); | ||
199 | + | ||
200 | + relations = relationService.findByTo(childB).get(); | ||
201 | + Assert.assertEquals(2, relations.size()); | ||
202 | + for (EntityRelation relation : relations) { | ||
203 | + Assert.assertEquals(childB, relation.getTo()); | ||
204 | + Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom())); | ||
205 | + } | ||
206 | + } | ||
207 | + | ||
208 | + @Test | ||
209 | + public void testCyclicRecursiveRelation() throws ExecutionException, InterruptedException { | ||
210 | + // A -> B -> C -> A | ||
211 | + AssetId assetA = new AssetId(UUIDs.timeBased()); | ||
212 | + AssetId assetB = new AssetId(UUIDs.timeBased()); | ||
213 | + AssetId assetC = new AssetId(UUIDs.timeBased()); | ||
214 | + | ||
215 | + EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); | ||
216 | + EntityRelation relationB = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE); | ||
217 | + EntityRelation relationC = new EntityRelation(assetC, assetA, EntityRelation.CONTAINS_TYPE); | ||
218 | + | ||
219 | + saveRelation(relationA); | ||
220 | + saveRelation(relationB); | ||
221 | + saveRelation(relationC); | ||
222 | + | ||
223 | + EntityRelationsQuery query = new EntityRelationsQuery(); | ||
224 | + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1)); | ||
225 | + query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); | ||
226 | + List<EntityRelation> relations = relationService.findByQuery(query).get(); | ||
227 | + Assert.assertEquals(3, relations.size()); | ||
228 | + Assert.assertTrue(relations.contains(relationA)); | ||
229 | + Assert.assertTrue(relations.contains(relationB)); | ||
230 | + Assert.assertTrue(relations.contains(relationC)); | ||
231 | + } | ||
232 | + | ||
233 | + @Test | ||
234 | + public void testRecursiveRelation() throws ExecutionException, InterruptedException { | ||
235 | + // A -> B -> [C,D] | ||
236 | + AssetId assetA = new AssetId(UUIDs.timeBased()); | ||
237 | + AssetId assetB = new AssetId(UUIDs.timeBased()); | ||
238 | + AssetId assetC = new AssetId(UUIDs.timeBased()); | ||
239 | + DeviceId deviceD = new DeviceId(UUIDs.timeBased()); | ||
240 | + | ||
241 | + EntityRelation relationAB = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); | ||
242 | + EntityRelation relationBC = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE); | ||
243 | + EntityRelation relationBD = new EntityRelation(assetB, deviceD, EntityRelation.CONTAINS_TYPE); | ||
244 | + | ||
245 | + | ||
246 | + saveRelation(relationAB); | ||
247 | + saveRelation(relationBC); | ||
248 | + saveRelation(relationBD); | ||
249 | + | ||
250 | + EntityRelationsQuery query = new EntityRelationsQuery(); | ||
251 | + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1)); | ||
252 | + query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); | ||
253 | + List<EntityRelation> relations = relationService.findByQuery(query).get(); | ||
254 | + Assert.assertEquals(2, relations.size()); | ||
255 | + Assert.assertTrue(relations.contains(relationAB)); | ||
256 | + Assert.assertTrue(relations.contains(relationBC)); | ||
257 | + } | ||
258 | + | ||
259 | + | ||
260 | + @Test(expected = DataValidationException.class) | ||
261 | + public void testSaveRelationWithEmptyFrom() throws ExecutionException, InterruptedException { | ||
262 | + EntityRelation relation = new EntityRelation(); | ||
263 | + relation.setTo(new AssetId(UUIDs.timeBased())); | ||
264 | + relation.setType(EntityRelation.CONTAINS_TYPE); | ||
265 | + Assert.assertTrue(saveRelation(relation)); | ||
266 | + } | ||
267 | + | ||
268 | + @Test(expected = DataValidationException.class) | ||
269 | + public void testSaveRelationWithEmptyTo() throws ExecutionException, InterruptedException { | ||
270 | + EntityRelation relation = new EntityRelation(); | ||
271 | + relation.setFrom(new AssetId(UUIDs.timeBased())); | ||
272 | + relation.setType(EntityRelation.CONTAINS_TYPE); | ||
273 | + Assert.assertTrue(saveRelation(relation)); | ||
274 | + } | ||
275 | + | ||
276 | + @Test(expected = DataValidationException.class) | ||
277 | + public void testSaveRelationWithEmptyType() throws ExecutionException, InterruptedException { | ||
278 | + EntityRelation relation = new EntityRelation(); | ||
279 | + relation.setFrom(new AssetId(UUIDs.timeBased())); | ||
280 | + relation.setTo(new AssetId(UUIDs.timeBased())); | ||
281 | + Assert.assertTrue(saveRelation(relation)); | ||
282 | + } | ||
283 | +} |