Commit 6b446023b139306ffe58cea69f57e3be02966aca
Committed by
GitHub
Merge pull request #151 from thingsboard/asset-alarm-mgmt
Asset & Alarm management
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 | 25 | import org.springframework.web.bind.annotation.ExceptionHandler; |
26 | 26 | import org.thingsboard.server.actors.service.ActorService; |
27 | 27 | import org.thingsboard.server.common.data.*; |
28 | +import org.thingsboard.server.common.data.alarm.Alarm; | |
29 | +import org.thingsboard.server.common.data.alarm.AlarmId; | |
28 | 30 | import org.thingsboard.server.common.data.asset.Asset; |
29 | 31 | import org.thingsboard.server.common.data.id.*; |
30 | 32 | import org.thingsboard.server.common.data.page.TextPageLink; |
... | ... | @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData; |
36 | 38 | import org.thingsboard.server.common.data.security.Authority; |
37 | 39 | import org.thingsboard.server.common.data.widget.WidgetType; |
38 | 40 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
41 | +import org.thingsboard.server.dao.alarm.AlarmService; | |
39 | 42 | import org.thingsboard.server.dao.asset.AssetService; |
40 | 43 | import org.thingsboard.server.dao.customer.CustomerService; |
41 | 44 | import org.thingsboard.server.dao.dashboard.DashboardService; |
... | ... | @@ -84,6 +87,9 @@ public abstract class BaseController { |
84 | 87 | protected AssetService assetService; |
85 | 88 | |
86 | 89 | @Autowired |
90 | + protected AlarmService alarmService; | |
91 | + | |
92 | + @Autowired | |
87 | 93 | protected DeviceCredentialsService deviceCredentialsService; |
88 | 94 | |
89 | 95 | @Autowired |
... | ... | @@ -334,6 +340,22 @@ public abstract class BaseController { |
334 | 340 | } |
335 | 341 | } |
336 | 342 | |
343 | + Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException { | |
344 | + try { | |
345 | + validateId(alarmId, "Incorrect alarmId " + alarmId); | |
346 | + Alarm alarm = alarmService.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 | 359 | WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException { |
338 | 360 | try { |
339 | 361 | validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); | ... | ... |
... | ... | @@ -16,28 +16,43 @@ |
16 | 16 | package org.thingsboard.server.common.data.alarm; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | +import lombok.AllArgsConstructor; | |
20 | +import lombok.Builder; | |
19 | 21 | import lombok.Data; |
20 | 22 | import org.thingsboard.server.common.data.BaseData; |
23 | +import org.thingsboard.server.common.data.id.AssetId; | |
21 | 24 | import org.thingsboard.server.common.data.HasName; |
22 | 25 | import org.thingsboard.server.common.data.id.EntityId; |
26 | +import org.thingsboard.server.common.data.id.TenantId; | |
23 | 27 | |
24 | 28 | /** |
25 | 29 | * Created by ashvayka on 11.05.17. |
26 | 30 | */ |
27 | 31 | @Data |
32 | +@Builder | |
33 | +@AllArgsConstructor | |
28 | 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 | 37 | private String type; |
35 | 38 | private EntityId originator; |
36 | 39 | private AlarmSeverity severity; |
37 | 40 | private AlarmStatus status; |
41 | + private long startTs; | |
42 | + private long endTs; | |
43 | + private long ackTs; | |
44 | + private long clearTs; | |
38 | 45 | private JsonNode details; |
39 | 46 | private boolean propagate; |
40 | 47 | |
48 | + public Alarm() { | |
49 | + super(); | |
50 | + } | |
51 | + | |
52 | + public Alarm(AlarmId id) { | |
53 | + super(id); | |
54 | + } | |
55 | + | |
41 | 56 | @Override |
42 | 57 | public String getName() { |
43 | 58 | return type; | ... | ... |
... | ... | @@ -15,14 +15,19 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.common.data.alarm; |
17 | 17 | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Builder; | |
18 | 20 | import lombok.Data; |
19 | 21 | import org.thingsboard.server.common.data.id.EntityId; |
22 | +import org.thingsboard.server.common.data.id.TenantId; | |
20 | 23 | import org.thingsboard.server.common.data.page.TimePageLink; |
21 | 24 | |
22 | 25 | /** |
23 | 26 | * Created by ashvayka on 11.05.17. |
24 | 27 | */ |
25 | 28 | @Data |
29 | +@Builder | |
30 | +@AllArgsConstructor | |
26 | 31 | public class AlarmQuery { |
27 | 32 | |
28 | 33 | private EntityId affectedEntityId; | ... | ... |
... | ... | @@ -22,4 +22,12 @@ public enum AlarmStatus { |
22 | 22 | |
23 | 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 | 16 | package org.thingsboard.server.common.data.id; |
17 | 17 | |
18 | 18 | import org.thingsboard.server.common.data.EntityType; |
19 | +import org.thingsboard.server.common.data.alarm.AlarmId; | |
19 | 20 | |
20 | 21 | import java.util.UUID; |
21 | 22 | |
... | ... | @@ -50,6 +51,8 @@ public class EntityIdFactory { |
50 | 51 | return new DeviceId(uuid); |
51 | 52 | case ASSET: |
52 | 53 | return new AssetId(uuid); |
54 | + case ALARM: | |
55 | + return new AlarmId(uuid); | |
53 | 56 | } |
54 | 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 | 127 | log.debug("Save entity {}", entity); |
128 | 128 | if (entity.getId() == null) { |
129 | 129 | entity.setId(UUIDs.timeBased()); |
130 | - } else { | |
130 | + } else if (isDeleteOnSave()) { | |
131 | 131 | removeById(entity.getId()); |
132 | 132 | } |
133 | 133 | Statement saveStatement = getSaveQuery(entity); |
... | ... | @@ -136,6 +136,10 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract |
136 | 136 | return new EntityResultSet<>(resultSet, entity); |
137 | 137 | } |
138 | 138 | |
139 | + protected boolean isDeleteOnSave() { | |
140 | + return true; | |
141 | + } | |
142 | + | |
139 | 143 | public T save(T entity) { |
140 | 144 | return saveWithResult(entity).getEntity(); |
141 | 145 | } |
... | ... | @@ -161,9 +165,18 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract |
161 | 165 | return getSession().execute(delete); |
162 | 166 | } |
163 | 167 | |
164 | - | |
165 | 168 | public List<T> find() { |
166 | 169 | log.debug("Get all entities from column family {}", getColumnFamilyName()); |
167 | 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 | 47 | return findPageWithTimeSearch(searchView, clauses, Collections.singletonList(ordering), pageLink); |
48 | 48 | } |
49 | 49 | |
50 | - | |
51 | 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 | 71 | Select select = select().from(searchView); |
53 | 72 | Where query = select.where(); |
54 | 73 | for (Clause clause : clauses) { |
... | ... | @@ -57,34 +76,35 @@ public abstract class AbstractSearchTimeDao<T extends BaseEntity<?>> extends Abs |
57 | 76 | query.limit(pageLink.getLimit()); |
58 | 77 | if (pageLink.isAscOrder()) { |
59 | 78 | if (pageLink.getIdOffset() != null) { |
60 | - query.and(QueryBuilder.gt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset())); | |
79 | + query.and(QueryBuilder.gt(idColumn, pageLink.getIdOffset())); | |
61 | 80 | } else if (pageLink.getStartTime() != null) { |
62 | 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 | 84 | if (pageLink.getEndTime() != null) { |
66 | 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 | 88 | } else { |
70 | 89 | if (pageLink.getIdOffset() != null) { |
71 | - query.and(QueryBuilder.lt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset())); | |
90 | + query.and(QueryBuilder.lt(idColumn, pageLink.getIdOffset())); | |
72 | 91 | } else if (pageLink.getEndTime() != null) { |
73 | 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 | 95 | if (pageLink.getStartTime() != null) { |
77 | 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 | 100 | List<Ordering> orderings = new ArrayList<>(topLevelOrderings); |
82 | 101 | if (pageLink.isAscOrder()) { |
83 | - orderings.add(QueryBuilder.asc(ModelConstants.ID_PROPERTY)); | |
102 | + orderings.add(QueryBuilder.asc(idColumn)); | |
84 | 103 | } else { |
85 | - orderings.add(QueryBuilder.desc(ModelConstants.ID_PROPERTY)); | |
104 | + orderings.add(QueryBuilder.desc(idColumn)); | |
86 | 105 | } |
87 | 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 | 15 | */ |
16 | 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 | 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 | 21 | import org.thingsboard.server.common.data.alarm.AlarmQuery; |
22 | 22 | import org.thingsboard.server.common.data.page.TimePageData; |
23 | 23 | |
24 | -import java.util.Optional; | |
25 | - | |
26 | 24 | /** |
27 | 25 | * Created by ashvayka on 11.05.17. |
28 | 26 | */ |
29 | 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 | 37 | ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query); |
44 | 38 | ... | ... |
... | ... | @@ -15,52 +15,277 @@ |
15 | 15 | */ |
16 | 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 | 21 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 22 | import lombok.extern.slf4j.Slf4j; |
23 | +import org.springframework.beans.factory.annotation.Autowired; | |
20 | 24 | import org.springframework.stereotype.Service; |
25 | +import org.springframework.util.StringUtils; | |
21 | 26 | import org.thingsboard.server.common.data.alarm.Alarm; |
22 | 27 | import org.thingsboard.server.common.data.alarm.AlarmId; |
23 | 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 | 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 | 56 | @Service |
29 | 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 | 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 | 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 | 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 | 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 | 200 | @Override |
63 | 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 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -148,6 +148,25 @@ public class ModelConstants { |
148 | 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 | 170 | * Cassandra entity relation constants. |
152 | 171 | */ |
153 | 172 | public static final String RELATION_COLUMN_FAMILY_NAME = "relation"; |
... | ... | @@ -157,6 +176,7 @@ public class ModelConstants { |
157 | 176 | public static final String RELATION_TO_TYPE_PROPERTY = "to_type"; |
158 | 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 | 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 | 16 | package org.thingsboard.server.dao.relation; |
17 | 17 | |
18 | 18 | import com.datastax.driver.core.*; |
19 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | |
20 | +import com.datastax.driver.core.querybuilder.Select; | |
19 | 21 | import com.fasterxml.jackson.databind.JsonNode; |
20 | 22 | import com.google.common.base.Function; |
21 | 23 | import com.google.common.util.concurrent.Futures; |
22 | 24 | import com.google.common.util.concurrent.ListenableFuture; |
23 | 25 | import lombok.extern.slf4j.Slf4j; |
24 | 26 | import org.springframework.stereotype.Component; |
27 | +import org.thingsboard.server.common.data.EntityType; | |
25 | 28 | import org.thingsboard.server.common.data.id.EntityId; |
26 | 29 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
30 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
27 | 31 | import org.thingsboard.server.common.data.relation.EntityRelation; |
28 | 32 | import org.thingsboard.server.dao.AbstractAsyncDao; |
33 | +import org.thingsboard.server.dao.AbstractSearchTimeDao; | |
29 | 34 | import org.thingsboard.server.dao.model.ModelConstants; |
30 | 35 | |
31 | 36 | import javax.annotation.Nullable; |
32 | 37 | import javax.annotation.PostConstruct; |
33 | 38 | import java.util.ArrayList; |
39 | +import java.util.Arrays; | |
34 | 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 | 46 | * Created by ashvayka on 25.04.17. |
38 | 47 | */ |
... | ... | @@ -145,6 +154,18 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { |
145 | 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 | 169 | private PreparedStatement getSaveStmt() { |
149 | 170 | if (saveStmt == null) { |
150 | 171 | saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + |
... | ... | @@ -235,31 +256,13 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { |
235 | 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 | 259 | private EntityId toEntity(Row row, String uuidColumn, String typeColumn) { |
248 | 260 | return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn)); |
249 | 261 | } |
250 | 262 | |
251 | 263 | private ListenableFuture<List<EntityRelation>> executeAsyncRead(EntityId from, BoundStatement stmt) { |
252 | 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 | 268 | private ListenableFuture<Boolean> getBooleanListenableFuture(ResultSetFuture rsFuture) { |
... | ... | @@ -276,4 +279,24 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { |
276 | 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 | 171 | RelationsSearchParameters params = query.getParameters(); |
172 | 172 | final List<EntityTypeFilter> filters = query.getFilters(); |
173 | 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 | 177 | int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE; |
... | ... | @@ -182,10 +181,14 @@ public class BaseRelationService implements RelationService { |
182 | 181 | return Futures.transform(relationSet, (Function<Set<EntityRelation>, List<EntityRelation>>) input -> { |
183 | 182 | List<EntityRelation> relations = new ArrayList<>(); |
184 | 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 | 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 | 262 | if (lvl == 0) { |
259 | 263 | return Futures.immediateFuture(Collections.emptySet()); |
260 | 264 | } | ... | ... |
... | ... | @@ -16,7 +16,9 @@ |
16 | 16 | package org.thingsboard.server.dao.relation; |
17 | 17 | |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | +import org.thingsboard.server.common.data.EntityType; | |
19 | 20 | import org.thingsboard.server.common.data.id.EntityId; |
21 | +import org.thingsboard.server.common.data.page.TimePageLink; | |
20 | 22 | import org.thingsboard.server.common.data.relation.EntityRelation; |
21 | 23 | |
22 | 24 | import java.util.List; |
... | ... | @@ -44,4 +46,6 @@ public interface RelationDao { |
44 | 46 | |
45 | 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 | 49 | |
50 | 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 | 277 | PRIMARY KEY ( (type, tenant_id), id, customer_id) |
278 | 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 | 305 | CREATE TABLE IF NOT EXISTS thingsboard.relation ( |
281 | 306 | from_id timeuuid, |
282 | 307 | from_type text, |
... | ... | @@ -285,7 +310,14 @@ CREATE TABLE IF NOT EXISTS thingsboard.relation ( |
285 | 310 | relation_type text, |
286 | 311 | additional_info text, |
287 | 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 | 322 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS |
291 | 323 | SELECT * | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.plugin.ComponentScope; |
40 | 40 | import org.thingsboard.server.common.data.plugin.ComponentType; |
41 | 41 | import org.thingsboard.server.common.data.plugin.PluginMetaData; |
42 | 42 | import org.thingsboard.server.common.data.rule.RuleMetaData; |
43 | +import org.thingsboard.server.dao.alarm.AlarmService; | |
43 | 44 | import org.thingsboard.server.dao.asset.AssetService; |
44 | 45 | import org.thingsboard.server.dao.component.ComponentDescriptorService; |
45 | 46 | import org.thingsboard.server.dao.customer.CustomerService; |
... | ... | @@ -119,6 +120,9 @@ public abstract class AbstractServiceTest { |
119 | 120 | protected RelationService relationService; |
120 | 121 | |
121 | 122 | @Autowired |
123 | + protected AlarmService alarmService; | |
124 | + | |
125 | + @Autowired | |
122 | 126 | private ComponentDescriptorService componentDescriptorService; |
123 | 127 | |
124 | 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 | +} | ... | ... |