diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.cql b/application/src/main/data/upgrade/2.1.1/schema_update.cql index d9ba517df7..1329a0b546 100644 --- a/application/src/main/data/upgrade/2.1.1/schema_update.cql +++ b/application/src/main/data/upgrade/2.1.1/schema_update.cql @@ -24,6 +24,7 @@ DROP TABLE IF EXISTS thingsboard.entity_views; CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( id timeuuid, entity_id timeuuid, + entity_type text, tenant_id timeuuid, customer_id timeuuid, name text, @@ -33,7 +34,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( search_text text, additional_info text, PRIMARY KEY (id, entity_id, tenant_id, customer_id) - ); +); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_name AS SELECT * diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 600820ec73..7f4f23a0ef 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -26,17 +26,11 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.controller.HttpValidationCallback; @@ -44,6 +38,7 @@ import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; @@ -66,6 +61,7 @@ public class AccessValidator { public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; + public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!"; @Autowired protected TenantService tenantService; @@ -88,6 +84,9 @@ public class AccessValidator { @Autowired protected RuleChainService ruleChainService; + @Autowired + protected EntityViewService entityViewService; + private ExecutorService executor; @PostConstruct @@ -158,6 +157,9 @@ public class AccessValidator { case TENANT: validateTenant(currentUser, entityId, callback); return; + case ENTITY_VIEW: + validateEntityView(currentUser, entityId, callback); + return; default: //TODO: add support of other entities throw new IllegalStateException("Not Implemented!"); @@ -293,6 +295,27 @@ public class AccessValidator { } } + private void validateEntityView(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + if (currentUser.isSystemAdmin()) { + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + ListenableFuture entityViewFuture = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId())); + Futures.addCallback(entityViewFuture, getCallback(callback, entityView -> { + if (entityView == null) { + return ValidationResult.entityNotFound(ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND); + } else { + if (!entityView.getTenantId().equals(currentUser.getTenantId())) { + return ValidationResult.accessDenied("Entity-view doesn't belong to the current Tenant!"); + } else if (currentUser.isCustomerUser() && !entityView.getCustomerId().equals(currentUser.getCustomerId())) { + return ValidationResult.accessDenied("Entity-view doesn't belong to the current Customer!"); + } else { + return ValidationResult.ok(entityView); + } + } + }), executor); + } + } + private FutureCallback getCallback(FutureCallback callback, Function> transformer) { return new FutureCallback() { @Override diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index a9e94e9184..c8a5da8151 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.controller.sql.EntityViewControllerSqlTest", + "org.thingsboard.server.controller.sql.*Test", }) public class ControllerSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java index ad066fc7ab..404e4e253f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java @@ -16,9 +16,11 @@ package org.thingsboard.server.controller.nosql; import org.thingsboard.server.controller.BaseEntityViewControllerTest; +import org.thingsboard.server.dao.service.DaoNoSqlTest; /** * Created by Victor Basanets on 8/27/2017. */ +@DaoNoSqlTest public class EntityViewControllerNoSqlTest extends BaseEntityViewControllerTest { } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index 813a9acd08..49dd20959f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java @@ -39,8 +39,8 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo private CustomerId customerId; private String name; private TelemetryEntityView keys; - private Long tsBegin; - private Long tsEnd; + private long startTs; + private long endTs; public EntityView() { super(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java index b1d270c934..1c32579b72 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java @@ -44,9 +44,4 @@ public class AttributesEntityView { public AttributesEntityView(AttributesEntityView obj) { this(obj.getCs(), obj.getSs(), obj.getSh()); } - - @Override - public String toString() { - return "{cs=" + cs + ", ss=" + ss + ", sh=" + sh + '}'; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java index e7398e6500..c899c65590 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java @@ -40,9 +40,4 @@ public class TelemetryEntityView { public TelemetryEntityView(TelemetryEntityView obj) { this(obj.getTimeseries(), obj.getAttributes()); } - - @Override - public String toString() { - return "{timeseries=" + timeseries + ", attributes=" + attributes + '}'; - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java new file mode 100644 index 0000000000..5716460d20 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java @@ -0,0 +1,108 @@ +/** + * Copyright © 2016-2018 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.entityview; + +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.querybuilder.Select; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.EntitySubtypeEntity; +import org.thingsboard.server.dao.model.nosql.EntityViewEntity; +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; +import org.thingsboard.server.dao.util.NoSqlDao; + +import java.util.*; + +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.*; + +/** + * Created by Victor Basanets on 9/06/2017. + */ +@Component +@Slf4j +@NoSqlDao +public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao implements EntityViewDao { + + @Override + protected Class getColumnFamilyClass() { + return EntityViewEntity.class; + } + + @Override + protected String getColumnFamilyName() { + return ENTITY_VIEW_TABLE_FAMILY_NAME; + } + + @Override + public EntityView save(EntityView domain) { + EntityView savedEntityView = super.save(domain); + EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW, + savedEntityView.getId().getEntityType().toString()); + EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype); + Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity); + executeWrite(saveStatement); + return savedEntityView; + } + + @Override + public List findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) { + log.debug("Try to find entity-views by tenantId [{}] and pageLink [{}]", tenantId, pageLink); + List entityViewEntities = + findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, + Collections.singletonList(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)), pageLink); + + log.trace("Found entity-views [{}] by tenantId [{}] and pageLink [{}]", entityViewEntities, tenantId, pageLink); + return DaoUtil.convertDataList(entityViewEntities); + } + + @Override + public Optional findEntityViewByTenantIdAndName(UUID tenantId, String entityViewName) { + return Optional.ofNullable(DaoUtil.getData( + findOneByStatement(select().from(ENTITY_VIEW_TENANT_AND_NAME_VIEW_NAME).where() + .and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)) + .and(eq(ENTITY_VIEW_NAME_PROPERTY, entityViewName)))) + ); + } + + @Override + public List findEntityViewByTenantIdAndEntityId(UUID tenantId, UUID entityId, TextPageLink pageLink) { + log.debug("Try to find entity-views by tenantId [{}], entityId[{}] and pageLink [{}]", tenantId, entityId, pageLink); + List entityViewEntities = findPageWithTextSearch(DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, + Arrays.asList(eq(DEVICE_CUSTOMER_ID_PROPERTY, entityId), + eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), + pageLink); + + log.trace("Found entity-views [{}] by tenantId [{}], entityId [{}] and pageLink [{}]", entityViewEntities, tenantId, entityId, pageLink); + return DaoUtil.convertDataList(entityViewEntities); + } + + @Override + public List findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { + return null; + } + + @Override + public List findEntityViewsByTenantIdAndCustomerIdAndEntityId(UUID tenantId, UUID customerId, UUID entityId, TextPageLink pageLink) { + return null; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java index 25404db834..5cbbef834f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java @@ -29,6 +29,14 @@ import java.util.UUID; */ public interface EntityViewDao extends Dao { + /** + * Save or update device object + * + * @param entityView the entity-view object + * @return saved entity-view object + */ + EntityView save(EntityView entityView); + /** * Find entity views by tenantId and page link. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java index 943d35e9b4..6de86be60c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java @@ -15,12 +15,10 @@ */ package org.thingsboard.server.dao.entityview; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; @@ -57,4 +55,6 @@ public interface EntityViewService { TextPageLink pageLink); void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId); + + ListenableFuture findEntityViewByIdAsync(EntityViewId entityViewId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 6554083fe4..31d3b30518 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -15,11 +15,14 @@ */ package org.thingsboard.server.dao.entityview; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityView; @@ -40,6 +43,8 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; import java.util.List; +import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -67,6 +72,10 @@ public class EntityViewServiceImpl extends AbstractEntityService @Autowired private CustomerDao customerDao; + @Autowired + private CacheManager cacheManager; + + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView findEntityViewById(EntityViewId entityViewId) { log.trace("Executing findEntityViewById [{}]", entityViewId); @@ -82,6 +91,7 @@ public class EntityViewServiceImpl extends AbstractEntityService .orElse(null); } + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}") @Override public EntityView saveEntityView(EntityView entityView) { log.trace("Executing save entity view [{}]", entityView); @@ -106,12 +116,14 @@ public class EntityViewServiceImpl extends AbstractEntityService @Override public void deleteEntityView(EntityViewId entityViewId) { log.trace("Executing deleteEntityView [{}]", entityViewId); + Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); deleteEntityRelations(entityViewId); EntityView entityView = entityViewDao.findById(entityViewId.getId()); List list = new ArrayList<>(); list.add(entityView.getTenantId()); list.add(entityView.getName()); + cache.evict(list); entityViewDao.removeById(entityViewId.getId()); } @@ -124,6 +136,7 @@ public class EntityViewServiceImpl extends AbstractEntityService return new TextPageData<>(entityViews, pageLink); } + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #entityId, #pageLink}") @Override public TextPageData findEntityViewByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TextPageLink pageLink) { @@ -163,6 +176,7 @@ public class EntityViewServiceImpl extends AbstractEntityService return new TextPageData<>(entityViews, pageLink); } + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #customerId, #entityId, #pageLink}") @Override public TextPageData findEntityViewsByTenantIdAndCustomerIdAndEntityId(TenantId tenantId, CustomerId customerId, @@ -190,6 +204,13 @@ public class EntityViewServiceImpl extends AbstractEntityService new CustomerEntityViewsUnAssigner(tenantId).removeEntities(customerId); } + @Override + public ListenableFuture findEntityViewByIdAsync(EntityViewId entityViewId) { + log.trace("Executing findEntityViewById [{}]", entityViewId); + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); + return entityViewDao.findByIdAsync(entityViewId.getId()); + } + private DataValidator entityViewValidator = new DataValidator() { 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 d06ddbd8e5..6932bea1a3 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 @@ -136,7 +136,6 @@ public class ModelConstants { public static final String DEVICE_NAME_PROPERTY = "name"; public static final String DEVICE_TYPE_PROPERTY = "type"; public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; - public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text"; @@ -152,11 +151,12 @@ public class ModelConstants { public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY; - public static final String ENTITY_VIEW_TYPE_PROPERTY = "type_entity"; + public static final String ENTITY_VIEW_TENANT_AND_NAME_VIEW_NAME = "entity_view_by_tenant_and_name"; public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys"; public static final String ENTITY_VIEW_TS_BEGIN_PROPERTY = "ts_begin"; public static final String ENTITY_VIEW_TS_END_PROPERTY = "ts_end"; public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; + public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text"; /** * Cassandra audit log constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java index 65914dd566..990ff704c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java @@ -19,24 +19,26 @@ import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.Table; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import org.hibernate.annotations.Type; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.objects.TelemetryEntityView; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import java.io.IOException; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; @@ -57,6 +59,10 @@ public class EntityViewEntity implements SearchTextEntity { @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) private UUID entityId; + @Enumerated(EnumType.STRING) + @Column(name = ENTITY_TYPE_PROPERTY) + private EntityType entityType; + @PartitionKey(value = 2) @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY) private UUID tenantId; @@ -68,9 +74,8 @@ public class EntityViewEntity implements SearchTextEntity { @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) private String name; - @Type(type = "json") @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY) - private JsonNode keys; + private String keys; @Column(name = ModelConstants.ENTITY_VIEW_TS_BEGIN_PROPERTY) private String tsBegin; @@ -85,6 +90,8 @@ public class EntityViewEntity implements SearchTextEntity { @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + private static final ObjectMapper mapper = new ObjectMapper(); + public EntityViewEntity() { super(); } @@ -95,6 +102,7 @@ public class EntityViewEntity implements SearchTextEntity { } if (entityView.getEntityId() != null) { this.entityId = entityView.getEntityId().getId(); + this.entityType = entityView.getEntityId().getEntityType(); } if (entityView.getTenantId() != null) { this.tenantId = entityView.getTenantId().getId(); @@ -103,13 +111,13 @@ public class EntityViewEntity implements SearchTextEntity { this.customerId = entityView.getCustomerId().getId(); } this.name = entityView.getName(); -// try { -// this.keys = entityView.getKeys(); -// } catch (IOException e) { -// e.printStackTrace(); -// } - this.tsBegin = entityView.getTsBegin() != null ? String.valueOf(entityView.getTsBegin()) : "0"; - this.tsEnd = entityView.getTsEnd() != null ? String.valueOf(entityView.getTsEnd()) : "0"; + try { + this.keys = mapper.writeValueAsString(entityView.getKeys()); + } catch (IOException e) { + e.printStackTrace(); + } + this.tsBegin = entityView.getStartTs() != 0L ? String.valueOf(entityView.getStartTs()) : "0"; + this.tsEnd = entityView.getEndTs() != 0L ? String.valueOf(entityView.getEndTs()) : "0"; this.searchText = entityView.getSearchText(); this.additionalInfo = entityView.getAdditionalInfo(); } @@ -124,7 +132,7 @@ public class EntityViewEntity implements SearchTextEntity { EntityView entityView = new EntityView(new EntityViewId(id)); entityView.setCreatedTime(UUIDs.unixTimestamp(id)); if (entityId != null) { - entityView.setEntityId(new DeviceId(entityId)); + entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), entityId.toString())); } if (tenantId != null) { entityView.setTenantId(new TenantId(tenantId)); @@ -133,13 +141,13 @@ public class EntityViewEntity implements SearchTextEntity { entityView.setCustomerId(new CustomerId(customerId)); } entityView.setName(name); -// try { -// entityView.setKeys((TelemetryEntityView) entityView.getKeys().toObject(keys)); -// } catch (IOException e) { -// e.printStackTrace(); -// } - entityView.setTsBegin(Long.parseLong(tsBegin)); - entityView.setTsEnd(Long.parseLong(tsEnd)); + try { + entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); + } catch (IOException e) { + e.printStackTrace(); + } + entityView.setStartTs(Long.parseLong(tsBegin)); + entityView.setEndTs(Long.parseLong(tsEnd)); entityView.setAdditionalInfo(additionalInfo); return entityView; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java index 9136717fe3..a89d3dec84 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java @@ -34,7 +34,6 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.*; import java.io.IOException; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; /** @@ -106,8 +105,8 @@ public class EntityViewEntity extends BaseSqlEntity implements Searc } catch (IOException e) { e.printStackTrace(); } - this.tsBegin = entityView.getTsBegin() != null ? String.valueOf(entityView.getTsBegin()) : "0"; - this.tsEnd = entityView.getTsEnd() != null ? String.valueOf(entityView.getTsEnd()) : "0"; + this.tsBegin = entityView.getStartTs() != 0L ? String.valueOf(entityView.getStartTs()) : "0"; + this.tsEnd = entityView.getEndTs() != 0L ? String.valueOf(entityView.getEndTs()) : "0"; this.searchText = entityView.getSearchText(); this.additionalInfo = entityView.getAdditionalInfo(); } @@ -142,8 +141,8 @@ public class EntityViewEntity extends BaseSqlEntity implements Searc } catch (IOException e) { e.printStackTrace(); } - entityView.setTsBegin(Long.parseLong(tsBegin)); - entityView.setTsEnd(Long.parseLong(tsEnd)); + entityView.setStartTs(Long.parseLong(tsBegin)); + entityView.setEndTs(Long.parseLong(tsEnd)); entityView.setAdditionalInfo(additionalInfo); return entityView; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index c981378939..9b837b24a5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -21,12 +21,18 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.kv.BaseTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvQuery; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.Validator; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -44,10 +50,17 @@ public class BaseTimeseriesService implements TimeseriesService { @Autowired private TimeseriesDao timeseriesDao; + @Autowired + private EntityViewService entityViewService; + @Override public ListenableFuture> findAll(EntityId entityId, List queries) { validate(entityId); queries.forEach(query -> validate(query)); + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); + return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries)); + } return timeseriesDao.findAllAsync(entityId, queries); } @@ -56,7 +69,13 @@ public class BaseTimeseriesService implements TimeseriesService { validate(entityId); List> futures = Lists.newArrayListWithExpectedSize(keys.size()); keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key)); - keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key))); + if (false/*entityId.getEntityType().equals(EntityType.ENTITY_VIEW)*/) { + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); + Collection newKeys = chooseKeysForEntityView(entityView, keys); + newKeys.forEach(newKey -> futures.add(timeseriesDao.findLatest(entityView.getEntityId(), newKey))); + } else { + keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key))); + } return Futures.allAsList(futures); } @@ -69,6 +88,11 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> save(EntityId entityId, TsKvEntry tsKvEntry) { validate(entityId); + try { + checkForNonEntityView(entityId); + } catch (Exception e) { + e.printStackTrace(); + } if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } @@ -79,6 +103,11 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> save(EntityId entityId, List tsKvEntries, long ttl) { + try { + checkForNonEntityView(entityId); + } catch (Exception e) { + e.printStackTrace(); + } List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); for (TsKvEntry tsKvEntry : tsKvEntries) { if (tsKvEntry == null) { @@ -90,11 +119,47 @@ public class BaseTimeseriesService implements TimeseriesService { } private void saveAndRegisterFutures(List> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + try { + checkForNonEntityView(entityId); + } catch (Exception e) { + e.printStackTrace(); + } futures.add(timeseriesDao.savePartition(entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); futures.add(timeseriesDao.saveLatest(entityId, tsKvEntry)); futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl)); } + private List updateQueriesForEntityView(EntityView entityView, List queries) { + List newQueries = new ArrayList<>(); + entityView.getKeys().getTimeseries() + .forEach(viewKey -> queries + .forEach(query -> { + if (query.getKey().equals(viewKey)) { + if (entityView.getStartTs() == 0 && entityView.getEndTs() == 0) { + newQueries.add(updateQuery(query.getStartTs(), query.getEndTs(), viewKey, query)); + } else if (entityView.getStartTs() == 0 && entityView.getEndTs() != 0) { + newQueries.add(updateQuery(query.getStartTs(), entityView.getEndTs(), viewKey, query)); + } else if (entityView.getStartTs() != 0 && entityView.getEndTs() == 0) { + newQueries.add(updateQuery(entityView.getStartTs(), query.getEndTs(), viewKey, query)); + } else { + newQueries.add(updateQuery(entityView.getStartTs(), entityView.getEndTs(), viewKey, query)); + } + }})); + return newQueries; + } + + @Deprecated /*Will be a modified*/ + private Collection chooseKeysForEntityView(EntityView entityView, Collection keys) { + Collection newKeys = new ArrayList<>(); + entityView.getKeys().getTimeseries() + .forEach(viewKey -> keys + .forEach(key -> { + if (key.equals(viewKey)) { + newKeys.add(key); + }})); + return newKeys; + } + private static void validate(EntityId entityId) { Validator.validateEntityId(entityId, "Incorrect entityId " + entityId); } @@ -108,4 +173,15 @@ public class BaseTimeseriesService implements TimeseriesService { throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty"); } } + + private static TsKvQuery updateQuery(Long startTs, Long endTs, String viewKey, TsKvQuery query) { + return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query : + new BaseTsKvQuery(viewKey, startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation()); + } + + private static void checkForNonEntityView(EntityId entityId) throws Exception { + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { + throw new Exception("Entity-views were read only"); + } + } } diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index bdd413d6ad..68f196f005 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -642,6 +642,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( id timeuuid, entity_id timeuuid, + entity_type text, tenant_id timeuuid, customer_id timeuuid, name text,