Commit e9267f7101ed5b6e9e412b2e6f08a42d0ee63e67

Authored by volodymyr-babak
2 parents 24d02763 6b446023

Merge branch 'master' into feature/mqtt-plugin

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 +}