diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index f6c62bee1f..669167000f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -16,6 +16,8 @@ package org.thingsboard.server.common.data.alarm; import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.AssetId; @@ -26,6 +28,8 @@ import org.thingsboard.server.common.data.id.TenantId; * Created by ashvayka on 11.05.17. */ @Data +@Builder +@AllArgsConstructor public class Alarm extends BaseData { private TenantId tenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java index 01346b0da9..72ee9a2d8f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java @@ -161,9 +161,18 @@ public abstract class AbstractModelDao> extends Abstract return getSession().execute(delete); } - public List find() { log.debug("Get all entities from column family {}", getColumnFamilyName()); return findListByStatement(QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel())); } + + protected static Function, T> toDataFunction() { + return new Function, T>() { + @Nullable + @Override + public T apply(@Nullable BaseEntity entity) { + return entity != null ? entity.toData() : null; + } + }; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 83286ac54a..e5383b6b0f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.model.AlarmEntity; +import java.util.UUID; + /** * Created by ashvayka on 11.05.17. */ @@ -29,6 +31,8 @@ public interface AlarmDao extends Dao { ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); + ListenableFuture findAlarmByIdAsync(UUID key); + AlarmEntity save(Alarm alarm); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java index a6f6ee7109..ba7e630802 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2017 The Thingsboard Authors - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,6 +15,11 @@ */ package org.thingsboard.server.dao.alarm; +import com.datastax.driver.core.querybuilder.Ordering; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Select; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -23,8 +28,16 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.AbstractModelDao; import org.thingsboard.server.dao.model.AlarmEntity; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.ModelConstants; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.UUID; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; +import static org.thingsboard.server.dao.model.ModelConstants.*; @Component @Slf4j @@ -48,6 +61,23 @@ public class AlarmDaoImpl extends AbstractModelDao implements Alarm @Override public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { - return null; + Select select = select().from(ALARM_COLUMN_FAMILY_NAME); + Select.Where query = select.where(); + query.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId())); + query.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, originator.getId())); + query.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, originator.getEntityType())); + query.and(eq(ALARM_TYPE_PROPERTY, type)); + query.limit(1); + query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY)); + return Futures.transform(findOneByStatementAsync(query), toDataFunction()); + } + + @Override + public ListenableFuture findAlarmByIdAsync(UUID key) { + log.debug("Get alarm by id {}", key); + Select.Where query = select().from(ALARM_BY_ID_VIEW_NAME).where(eq(ModelConstants.ID_PROPERTY, key)); + query.limit(1); + log.trace("Execute query {}", query); + return Futures.transform(findOneByStatementAsync(query), toDataFunction()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index a3daafca6e..6508db21a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -16,9 +16,11 @@ package org.thingsboard.server.dao.alarm; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmQuery; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.TimePageData; @@ -37,6 +39,8 @@ public interface AlarmService { ListenableFuture clearAlarm(AlarmId alarmId, long ackTs); + ListenableFuture findAlarmById(AlarmId alarmId); + ListenableFuture> findAlarms(AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index b9df342018..120e6b75d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2017 The Thingsboard Authors - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -91,6 +91,9 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService if (alarm.getStartTs() == 0L) { alarm.setStartTs(System.currentTimeMillis()); } + if (alarm.getEndTs() == 0L) { + alarm.setEndTs(alarm.getStartTs()); + } Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); if (existing == null || existing.getStatus().isCleared()) { log.debug("New Alarm : {}", alarm); @@ -117,11 +120,10 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService @Override public ListenableFuture updateAlarm(Alarm update) { alarmDataValidator.validate(update); - return getAndUpdate(update.getId(), new Function() { + return getAndUpdate(update.getId(), new Function() { @Nullable @Override - public Boolean apply(@Nullable AlarmEntity entity) { - Alarm alarm = getData(entity); + public Boolean apply(@Nullable Alarm alarm) { if (alarm == null) { return false; } else { @@ -139,11 +141,10 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService @Override public ListenableFuture ackAlarm(AlarmId alarmId, long ackTime) { - return getAndUpdate(alarmId, new Function() { + return getAndUpdate(alarmId, new Function() { @Nullable @Override - public Boolean apply(@Nullable AlarmEntity entity) { - Alarm alarm = getData(entity); + public Boolean apply(@Nullable Alarm alarm) { if (alarm == null || alarm.getStatus().isAck()) { return false; } else { @@ -161,11 +162,10 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService @Override public ListenableFuture clearAlarm(AlarmId alarmId, long clearTime) { - return getAndUpdate(alarmId, new Function() { + return getAndUpdate(alarmId, new Function() { @Nullable @Override - public Boolean apply(@Nullable AlarmEntity entity) { - Alarm alarm = getData(entity); + public Boolean apply(@Nullable Alarm alarm) { if (alarm == null || alarm.getStatus().isCleared()) { return false; } else { @@ -181,6 +181,13 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService }); } + @Override + public ListenableFuture findAlarmById(AlarmId alarmId) { + log.trace("Executing findAlarmById [{}]", alarmId); + validateId(alarmId, "Incorrect alarmId " + alarmId); + return alarmDao.findAlarmByIdAsync(alarmId.getId()); + } + @Override public ListenableFuture> findAlarms(AlarmQuery query) { return null; @@ -230,9 +237,9 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService } } - private ListenableFuture getAndUpdate(AlarmId alarmId, Function function) { + private ListenableFuture getAndUpdate(AlarmId alarmId, Function function) { validateId(alarmId, "Alarm id should be specified!"); - ListenableFuture entity = alarmDao.findByIdAsync(alarmId.getId()); + ListenableFuture entity = alarmDao.findAlarmByIdAsync(alarmId.getId()); return Futures.transform(entity, function); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java index 7dba381d65..62cd08a218 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java @@ -125,6 +125,94 @@ public final class AlarmEntity implements BaseEntity { this.tenantId = tenantId; } + public UUID getOriginatorId() { + return originatorId; + } + + public void setOriginatorId(UUID originatorId) { + this.originatorId = originatorId; + } + + public EntityType getOriginatorType() { + return originatorType; + } + + public void setOriginatorType(EntityType originatorType) { + this.originatorType = originatorType; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public AlarmSeverity getSeverity() { + return severity; + } + + public void setSeverity(AlarmSeverity severity) { + this.severity = severity; + } + + public AlarmStatus getStatus() { + return status; + } + + public void setStatus(AlarmStatus status) { + this.status = status; + } + + public Long getStartTs() { + return startTs; + } + + public void setStartTs(Long startTs) { + this.startTs = startTs; + } + + public Long getEndTs() { + return endTs; + } + + public void setEndTs(Long endTs) { + this.endTs = endTs; + } + + public Long getAckTs() { + return ackTs; + } + + public void setAckTs(Long ackTs) { + this.ackTs = ackTs; + } + + public Long getClearTs() { + return clearTs; + } + + public void setClearTs(Long clearTs) { + this.clearTs = clearTs; + } + + public JsonNode getDetails() { + return details; + } + + public void setDetails(JsonNode details) { + this.details = details; + } + + public Boolean getPropagate() { + return propagate; + } + + public void setPropagate(Boolean propagate) { + this.propagate = propagate; + } + @Override public Alarm toData() { Alarm alarm = new Alarm(new AlarmId(id)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 6a002b0c12..2efbd5dfd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -157,6 +157,8 @@ public class ModelConstants { public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts"; public static final String ALARM_PROPAGATE_PROPERTY = "propagate"; + public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; + /** * Cassandra entity relation constants. */ diff --git a/dao/src/main/resources/schema.cql b/dao/src/main/resources/schema.cql index 797e4a1f19..b86031b76e 100644 --- a/dao/src/main/resources/schema.cql +++ b/dao/src/main/resources/schema.cql @@ -250,7 +250,15 @@ CREATE TABLE IF NOT EXISTS thingsboard.alarm ( details text, propagate boolean, PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id) -); +) WITH CLUSTERING ORDER BY ( type ASC, id DESC); + +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.alarm_by_id AS + SELECT * + from thingsboard.alarm + WHERE tenant_id IS NOT NULL AND originator_id IS NOT NULL AND originator_type IS NOT NULL AND type IS NOT NULL + AND type IS NOT NULL AND id IS NOT NULL + PRIMARY KEY (id, tenant_id, originator_id, originator_type, type) + WITH CLUSTERING ORDER BY ( tenant_id ASC, originator_id ASC, originator_type ASC, type ASC); CREATE TABLE IF NOT EXISTS thingsboard.relation ( from_id timeuuid, diff --git a/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java index b1502595cb..82f9439fcd 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java @@ -25,12 +25,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*Test", - "org.thingsboard.server.dao.kv.*Test", - "org.thingsboard.server.dao.plugin.*Test", - "org.thingsboard.server.dao.rule.*Test", - "org.thingsboard.server.dao.attributes.*Test", - "org.thingsboard.server.dao.timeseries.*Test" + "org.thingsboard.server.dao.service.AlarmServiceTest", +// "org.thingsboard.server.dao.service.*Test", +// "org.thingsboard.server.dao.kv.*Test", +// "org.thingsboard.server.dao.plugin.*Test", +// "org.thingsboard.server.dao.rule.*Test", +// "org.thingsboard.server.dao.attributes.*Test", +// "org.thingsboard.server.dao.timeseries.*Test" }) public class DaoTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 903207c1d5..d724d7f0ad 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -32,6 +32,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; @@ -40,6 +41,7 @@ import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.component.ComponentDescriptorService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; @@ -114,6 +116,9 @@ public abstract class AbstractServiceTest { @Autowired protected RelationService relationService; + @Autowired + protected AlarmService alarmService; + @Autowired private ComponentDescriptorService componentDescriptorService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java new file mode 100644 index 0000000000..8b905218a4 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java @@ -0,0 +1,98 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service; + +import com.datastax.driver.core.utils.UUIDs; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.relation.EntityRelationsQuery; +import org.thingsboard.server.dao.relation.EntitySearchDirection; +import org.thingsboard.server.dao.relation.EntityTypeFilter; +import org.thingsboard.server.dao.relation.RelationsSearchParameters; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class AlarmServiceTest extends AbstractServiceTest { + + public static final String TEST_ALARM = "TEST_ALARM"; + private TenantId tenantId; + + @Before + public void before() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + } + + @After + public void after() { + tenantService.deleteTenant(tenantId); + } + + + @Test + public void testSaveAndFetchAlarm() throws ExecutionException, InterruptedException { + AssetId parentId = new AssetId(UUIDs.timeBased()); + AssetId childId = new AssetId(UUIDs.timeBased()); + + EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); + + Assert.assertTrue(relationService.saveRelation(relation).get()); + + long ts = System.currentTimeMillis(); + Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId) + .type(TEST_ALARM) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + + Alarm created = alarmService.createOrUpdateAlarm(alarm); + + Assert.assertNotNull(created); + Assert.assertNotNull(created.getId()); + Assert.assertNotNull(created.getOriginator()); + Assert.assertNotNull(created.getSeverity()); + Assert.assertNotNull(created.getStatus()); + + Assert.assertEquals(tenantId, created.getTenantId()); + Assert.assertEquals(childId, created.getOriginator()); + Assert.assertEquals(TEST_ALARM, created.getType()); + Assert.assertEquals(AlarmSeverity.CRITICAL, created.getSeverity()); + Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, created.getStatus()); + Assert.assertEquals(ts, created.getStartTs()); + Assert.assertEquals(ts, created.getEndTs()); + Assert.assertEquals(0L, created.getAckTs()); + Assert.assertEquals(0L, created.getClearTs()); + + Alarm fetched = alarmService.findAlarmById(created.getId()).get(); + Assert.assertEquals(created, fetched); + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java similarity index 99% rename from dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java index 3f5c55affd..376a88dc49 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java @@ -36,7 +36,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -public class RelationServiceImplTest extends AbstractServiceTest { +public class RelationServiceTest extends AbstractServiceTest { @Before public void before() {