Commit 6b446023b139306ffe58cea69f57e3be02966aca

Authored by Andrew Shvayka
Committed by GitHub
2 parents e3976b31 9f3de065

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