Commit 7d7d5cbdbca19e5636c0ab0cef0922affd9bde1c

Authored by Igor Kulikov
2 parents ee3d75bb 6475c696

Merge branch 'master' into develop/2.2

Showing 92 changed files with 3729 additions and 144 deletions

Too many changes to show.

To preserve performance only 92 of 105 files are displayed.

  1 +--
  2 +-- Copyright © 2016-2018 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 +
  17 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
  18 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
  19 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
  20 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
  21 +
  22 +DROP TABLE IF EXISTS thingsboard.entity_views;
  23 +ControllerSqlTestSuite
  24 +CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
  25 + id timeuuid,
  26 + entity_id timeuuid,
  27 + entity_type text,
  28 + tenant_id timeuuid,
  29 + customer_id timeuuid,
  30 + name text,
  31 + keys text,
  32 + start_ts bigint,
  33 + end_ts bigint,
  34 + search_text text,
  35 + additional_info text,
  36 + PRIMARY KEY (id, entity_id, tenant_id, customer_id)
  37 +);
  38 +
  39 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
  40 + SELECT *
  41 + from thingsboard.entity_views
  42 + WHERE tenant_id IS NOT NULL
  43 + AND entity_id IS NOT NULL
  44 + AND customer_id IS NOT NULL
  45 + AND name IS NOT NULL
  46 + AND id IS NOT NULL
  47 + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
  48 + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
  49 +
  50 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
  51 + SELECT *
  52 + from thingsboard.entity_views
  53 + WHERE tenant_id IS NOT NULL
  54 + AND entity_id IS NOT NULL
  55 + AND customer_id IS NOT NULL
  56 + AND search_text IS NOT NULL
  57 + AND id IS NOT NULL
  58 + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
  59 + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
  60 +
  61 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
  62 + SELECT *
  63 + from thingsboard.entity_views
  64 + WHERE tenant_id IS NOT NULL
  65 + AND customer_id IS NOT NULL
  66 + AND entity_id IS NOT NULL
  67 + AND search_text IS NOT NULL
  68 + AND id IS NOT NULL
  69 + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
  70 + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
  71 +
  72 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
  73 + SELECT *
  74 + from thingsboard.entity_views
  75 + WHERE tenant_id IS NOT NULL
  76 + AND customer_id IS NOT NULL
  77 + AND entity_id IS NOT NULL
  78 + AND search_text IS NOT NULL
  79 + AND id IS NOT NULL
  80 + PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
  81 + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
... ...
  1 +--
  2 +-- Copyright © 2016-2018 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 +
  17 +DROP TABLE IF EXISTS entity_views;
  18 +
  19 +CREATE TABLE IF NOT EXISTS entity_views (
  20 + id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
  21 + entity_id varchar(31),
  22 + entity_type varchar(255),
  23 + tenant_id varchar(31),
  24 + customer_id varchar(31),
  25 + name varchar(255),
  26 + keys varchar(255),
  27 + start_ts bigint,
  28 + end_ts bigint,
  29 + search_text varchar(255),
  30 + additional_info varchar
  31 +);
... ...
... ... @@ -48,6 +48,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
48 48 import org.thingsboard.server.dao.audit.AuditLogService;
49 49 import org.thingsboard.server.dao.customer.CustomerService;
50 50 import org.thingsboard.server.dao.device.DeviceService;
  51 +import org.thingsboard.server.dao.entityview.EntityViewService;
51 52 import org.thingsboard.server.dao.event.EventService;
52 53 import org.thingsboard.server.dao.relation.RelationService;
53 54 import org.thingsboard.server.dao.rule.RuleChainService;
... ... @@ -161,6 +162,10 @@ public class ActorSystemContext {
161 162
162 163 @Autowired
163 164 @Getter
  165 + private EntityViewService entityViewService;
  166 +
  167 + @Autowired
  168 + @Getter
164 169 private TelemetrySubscriptionService tsSubService;
165 170
166 171 @Autowired
... ...
... ... @@ -41,6 +41,7 @@ import org.thingsboard.server.dao.asset.AssetService;
41 41 import org.thingsboard.server.dao.attributes.AttributesService;
42 42 import org.thingsboard.server.dao.customer.CustomerService;
43 43 import org.thingsboard.server.dao.device.DeviceService;
  44 +import org.thingsboard.server.dao.entityview.EntityViewService;
44 45 import org.thingsboard.server.dao.relation.RelationService;
45 46 import org.thingsboard.server.dao.rule.RuleChainService;
46 47 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -213,6 +214,11 @@ class DefaultTbContext implements TbContext {
213 214 }
214 215
215 216 @Override
  217 + public EntityViewService getEntityViewService() {
  218 + return mainCtx.getEntityViewService();
  219 + }
  220 +
  221 + @Override
216 222 public MailService getMailService() {
217 223 if (mainCtx.isAllowSystemMailService()) {
218 224 return mainCtx.getMailService();
... ...
... ... @@ -56,6 +56,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
56 56 import org.thingsboard.server.dao.dashboard.DashboardService;
57 57 import org.thingsboard.server.dao.device.DeviceCredentialsService;
58 58 import org.thingsboard.server.dao.device.DeviceService;
  59 +import org.thingsboard.server.dao.entityview.EntityViewService;
59 60 import org.thingsboard.server.dao.exception.DataValidationException;
60 61 import org.thingsboard.server.dao.exception.IncorrectParameterException;
61 62 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -139,6 +140,9 @@ public abstract class BaseController {
139 140 @Autowired
140 141 protected DeviceStateService deviceStateService;
141 142
  143 + @Autowired
  144 + protected EntityViewService entityViewService;
  145 +
142 146 @ExceptionHandler(ThingsboardException.class)
143 147 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
144 148 errorResponseHandler.handle(ex, response);
... ... @@ -313,6 +317,9 @@ public abstract class BaseController {
313 317 case USER:
314 318 checkUserId(new UserId(entityId.getId()));
315 319 return;
  320 + case ENTITY_VIEW:
  321 + checkEntityViewId(new EntityViewId(entityId.getId()));
  322 + return;
316 323 default:
317 324 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
318 325 }
... ... @@ -340,6 +347,25 @@ public abstract class BaseController {
340 347 }
341 348 }
342 349
  350 + protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException {
  351 + try {
  352 + validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
  353 + EntityView entityView = entityViewService.findEntityViewById(entityViewId);
  354 + checkEntityView(entityView);
  355 + return entityView;
  356 + } catch (Exception e) {
  357 + throw handleException(e, false);
  358 + }
  359 + }
  360 +
  361 + protected void checkEntityView(EntityView entityView) throws ThingsboardException {
  362 + checkNotNull(entityView);
  363 + checkTenantId(entityView.getTenantId());
  364 + if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
  365 + checkCustomerId(entityView.getCustomerId());
  366 + }
  367 + }
  368 +
343 369 Asset checkAssetId(AssetId assetId) throws ThingsboardException {
344 370 try {
345 371 validateId(assetId, "Incorrect assetId " + assetId);
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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 org.springframework.http.HttpStatus;
  19 +import org.springframework.security.access.prepost.PreAuthorize;
  20 +import org.springframework.web.bind.annotation.PathVariable;
  21 +import org.springframework.web.bind.annotation.RequestBody;
  22 +import org.springframework.web.bind.annotation.RequestMapping;
  23 +import org.springframework.web.bind.annotation.RequestMethod;
  24 +import org.springframework.web.bind.annotation.RequestParam;
  25 +import org.springframework.web.bind.annotation.ResponseBody;
  26 +import org.springframework.web.bind.annotation.ResponseStatus;
  27 +import org.springframework.web.bind.annotation.RestController;
  28 +import org.thingsboard.server.common.data.Customer;
  29 +import org.thingsboard.server.common.data.EntityType;
  30 +import org.thingsboard.server.common.data.EntityView;
  31 +import org.thingsboard.server.common.data.audit.ActionType;
  32 +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
  33 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  34 +import org.thingsboard.server.common.data.id.CustomerId;
  35 +import org.thingsboard.server.common.data.id.EntityViewId;
  36 +import org.thingsboard.server.common.data.id.TenantId;
  37 +import org.thingsboard.server.common.data.page.TextPageData;
  38 +import org.thingsboard.server.common.data.page.TextPageLink;
  39 +import org.thingsboard.server.dao.exception.IncorrectParameterException;
  40 +import org.thingsboard.server.dao.model.ModelConstants;
  41 +
  42 +import java.util.List;
  43 +import java.util.stream.Collectors;
  44 +
  45 +import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
  46 +
  47 +/**
  48 + * Created by Victor Basanets on 8/28/2017.
  49 + */
  50 +@RestController
  51 +@RequestMapping("/api")
  52 +public class EntityViewController extends BaseController {
  53 +
  54 + public static final String ENTITY_VIEW_ID = "entityViewId";
  55 +
  56 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  57 + @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET)
  58 + @ResponseBody
  59 + public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  60 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  61 + try {
  62 + return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)));
  63 + } catch (Exception e) {
  64 + throw handleException(e);
  65 + }
  66 + }
  67 +
  68 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  69 + @RequestMapping(value = "/entityView", method = RequestMethod.POST)
  70 + @ResponseBody
  71 + public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException {
  72 + try {
  73 + entityView.setTenantId(getCurrentUser().getTenantId());
  74 + EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
  75 + logEntityAction(savedEntityView.getId(), savedEntityView, null,
  76 + entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  77 + return savedEntityView;
  78 + } catch (Exception e) {
  79 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null,
  80 + entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  81 + throw handleException(e);
  82 + }
  83 + }
  84 +
  85 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  86 + @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE)
  87 + @ResponseStatus(value = HttpStatus.OK)
  88 + public void deleteEntityView(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  89 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  90 + try {
  91 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  92 + EntityView entityView = checkEntityViewId(entityViewId);
  93 + entityViewService.deleteEntityView(entityViewId);
  94 + logEntityAction(entityViewId, entityView, entityView.getCustomerId(),
  95 + ActionType.DELETED,null, strEntityViewId);
  96 + } catch (Exception e) {
  97 + logEntityAction(emptyId(EntityType.ENTITY_VIEW),
  98 + null,
  99 + null,
  100 + ActionType.DELETED, e, strEntityViewId);
  101 + throw handleException(e);
  102 + }
  103 + }
  104 +
  105 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  106 + @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST)
  107 + @ResponseBody
  108 + public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId,
  109 + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  110 + checkParameter(CUSTOMER_ID, strCustomerId);
  111 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  112 + try {
  113 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  114 + Customer customer = checkCustomerId(customerId);
  115 +
  116 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  117 + checkEntityViewId(entityViewId);
  118 +
  119 + EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId));
  120 + logEntityAction(entityViewId, savedEntityView,
  121 + savedEntityView.getCustomerId(),
  122 + ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName());
  123 + return savedEntityView;
  124 + } catch (Exception e) {
  125 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
  126 + null,
  127 + ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId);
  128 + throw handleException(e);
  129 + }
  130 + }
  131 +
  132 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  133 + @RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE)
  134 + @ResponseBody
  135 + public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  136 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  137 + try {
  138 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  139 + EntityView entityView = checkEntityViewId(entityViewId);
  140 + if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
  141 + throw new IncorrectParameterException("Entity View isn't assigned to any customer!");
  142 + }
  143 + Customer customer = checkCustomerId(entityView.getCustomerId());
  144 + EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId));
  145 + logEntityAction(entityViewId, entityView,
  146 + entityView.getCustomerId(),
  147 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName());
  148 +
  149 + return savedEntityView;
  150 + } catch (Exception e) {
  151 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
  152 + null,
  153 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId);
  154 + throw handleException(e);
  155 + }
  156 + }
  157 +
  158 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  159 + @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET)
  160 + @ResponseBody
  161 + public TextPageData<EntityView> getCustomerEntityViews(
  162 + @PathVariable("customerId") String strCustomerId,
  163 + @RequestParam int limit,
  164 + @RequestParam(required = false) String textSearch,
  165 + @RequestParam(required = false) String idOffset,
  166 + @RequestParam(required = false) String textOffset) throws ThingsboardException {
  167 + checkParameter("customerId", strCustomerId);
  168 + try {
  169 + TenantId tenantId = getCurrentUser().getTenantId();
  170 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  171 + checkCustomerId(customerId);
  172 + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  173 + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  174 + } catch (Exception e) {
  175 + throw handleException(e);
  176 + }
  177 + }
  178 +
  179 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  180 + @RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET)
  181 + @ResponseBody
  182 + public TextPageData<EntityView> getTenantEntityViews(
  183 + @RequestParam int limit,
  184 + @RequestParam(required = false) String textSearch,
  185 + @RequestParam(required = false) String idOffset,
  186 + @RequestParam(required = false) String textOffset) throws ThingsboardException {
  187 + try {
  188 + TenantId tenantId = getCurrentUser().getTenantId();
  189 + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  190 + return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
  191 + } catch (Exception e) {
  192 + throw handleException(e);
  193 + }
  194 + }
  195 +
  196 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  197 + @RequestMapping(value = "/entityViews", method = RequestMethod.POST)
  198 + @ResponseBody
  199 + public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
  200 + checkNotNull(query);
  201 + checkNotNull(query.getParameters());
  202 + checkEntityId(query.getParameters().getEntityId());
  203 + try {
  204 + List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
  205 + entityViews = entityViews.stream().filter(entityView -> {
  206 + try {
  207 + checkEntityView(entityView);
  208 + return true;
  209 + } catch (ThingsboardException e) {
  210 + return false;
  211 + }
  212 + }).collect(Collectors.toList());
  213 + return entityViews;
  214 + } catch (Exception e) {
  215 + throw handleException(e);
  216 + }
  217 + }
  218 +}
... ...
... ... @@ -24,9 +24,10 @@ import org.springframework.context.annotation.Profile;
24 24 import org.springframework.stereotype.Service;
25 25 import org.thingsboard.server.service.component.ComponentDiscoveryService;
26 26 import org.thingsboard.server.service.install.DataUpdateService;
27   -import org.thingsboard.server.service.install.DatabaseSchemaService;
28 27 import org.thingsboard.server.service.install.DatabaseUpgradeService;
  28 +import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
29 29 import org.thingsboard.server.service.install.SystemDataLoaderService;
  30 +import org.thingsboard.server.service.install.TsDatabaseSchemaService;
30 31
31 32 @Service
32 33 @Profile("install")
... ... @@ -43,7 +44,10 @@ public class ThingsboardInstallService {
43 44 private Boolean loadDemo;
44 45
45 46 @Autowired
46   - private DatabaseSchemaService databaseSchemaService;
  47 + private EntityDatabaseSchemaService entityDatabaseSchemaService;
  48 +
  49 + @Autowired
  50 + private TsDatabaseSchemaService tsDatabaseSchemaService;
47 51
48 52 @Autowired
49 53 private DatabaseUpgradeService databaseUpgradeService;
... ... @@ -88,6 +92,11 @@ public class ThingsboardInstallService {
88 92
89 93 dataUpdateService.updateData("1.4.0");
90 94
  95 + case "2.0.0":
  96 + log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ...");
  97 +
  98 + databaseUpgradeService.upgradeDatabase("2.0.0");
  99 +
91 100 log.info("Updating system data...");
92 101
93 102 systemDataLoaderService.deleteSystemWidgetBundle("charts");
... ... @@ -114,9 +123,13 @@ public class ThingsboardInstallService {
114 123
115 124 log.info("Starting ThingsBoard Installation...");
116 125
117   - log.info("Installing DataBase schema...");
  126 + log.info("Installing DataBase schema for entities...");
  127 +
  128 + entityDatabaseSchemaService.createDatabaseSchema();
  129 +
  130 + log.info("Installing DataBase schema for timeseries...");
118 131
119   - databaseSchemaService.createDatabaseSchema();
  132 + tsDatabaseSchemaService.createDatabaseSchema();
120 133
121 134 log.info("Loading system data...");
122 135
... ...
application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java renamed from application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
... ... @@ -17,24 +17,17 @@ package org.thingsboard.server.service.install;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20   -import org.springframework.context.annotation.Profile;
21   -import org.springframework.stereotype.Service;
22 20 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
23   -import org.thingsboard.server.dao.util.NoSqlDao;
24 21 import org.thingsboard.server.service.install.cql.CQLStatementsParser;
25 22
26 23 import java.nio.file.Path;
27 24 import java.nio.file.Paths;
28 25 import java.util.List;
29 26
30   -@Service
31   -@NoSqlDao
32   -@Profile("install")
33 27 @Slf4j
34   -public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
  28 +public abstract class CassandraAbstractDatabaseSchemaService implements DatabaseSchemaService {
35 29
36 30 private static final String CASSANDRA_DIR = "cassandra";
37   - private static final String SCHEMA_CQL = "schema.cql";
38 31
39 32 @Autowired
40 33 private CassandraInstallCluster cluster;
... ... @@ -42,10 +35,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
42 35 @Autowired
43 36 private InstallScripts installScripts;
44 37
  38 + private final String schemaCql;
  39 +
  40 + protected CassandraAbstractDatabaseSchemaService(String schemaCql) {
  41 + this.schemaCql = schemaCql;
  42 + }
  43 +
45 44 @Override
46 45 public void createDatabaseSchema() throws Exception {
47   - log.info("Installing Cassandra DataBase schema...");
48   - Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL);
  46 + log.info("Installing Cassandra DataBase schema part: " + schemaCql);
  47 + Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, schemaCql);
49 48 loadCql(schemaFile);
50 49
51 50 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.service.install;
  17 +
  18 +import org.springframework.context.annotation.Profile;
  19 +import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.dao.util.NoSqlDao;
  21 +
  22 +@Service
  23 +@NoSqlDao
  24 +@Profile("install")
  25 +public class CassandraEntityDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
  26 + implements EntityDatabaseSchemaService {
  27 + public CassandraEntityDatabaseSchemaService() {
  28 + super("schema-entities.cql");
  29 + }
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.service.install;
  17 +
  18 +import org.springframework.context.annotation.Profile;
  19 +import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.dao.util.NoSqlTsDao;
  21 +
  22 +@Service
  23 +@NoSqlTsDao
  24 +@Profile("install")
  25 +public class CassandraTsDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
  26 + implements TsDatabaseSchemaService {
  27 + public CassandraTsDatabaseSchemaService() {
  28 + super("schema-ts.cql");
  29 + }
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.service.install;
  17 +
  18 +public interface EntityDatabaseSchemaService extends DatabaseSchemaService {
  19 +}
... ...
application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java renamed from application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
... ... @@ -18,9 +18,6 @@ package org.thingsboard.server.service.install;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.beans.factory.annotation.Value;
21   -import org.springframework.context.annotation.Profile;
22   -import org.springframework.stereotype.Service;
23   -import org.thingsboard.server.dao.util.SqlDao;
24 21
25 22 import java.nio.charset.Charset;
26 23 import java.nio.file.Files;
... ... @@ -29,14 +26,10 @@ import java.nio.file.Paths;
29 26 import java.sql.Connection;
30 27 import java.sql.DriverManager;
31 28
32   -@Service
33   -@Profile("install")
34 29 @Slf4j
35   -@SqlDao
36   -public class SqlDatabaseSchemaService implements DatabaseSchemaService {
  30 +public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService {
37 31
38 32 private static final String SQL_DIR = "sql";
39   - private static final String SCHEMA_SQL = "schema.sql";
40 33
41 34 @Value("${spring.datasource.url}")
42 35 private String dbUrl;
... ... @@ -50,12 +43,18 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
50 43 @Autowired
51 44 private InstallScripts installScripts;
52 45
  46 + private final String schemaSql;
  47 +
  48 + protected SqlAbstractDatabaseSchemaService(String schemaSql) {
  49 + this.schemaSql = schemaSql;
  50 + }
  51 +
53 52 @Override
54 53 public void createDatabaseSchema() throws Exception {
55 54
56   - log.info("Installing SQL DataBase schema...");
  55 + log.info("Installing SQL DataBase schema part: " + schemaSql);
57 56
58   - Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL);
  57 + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaSql);
59 58 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
60 59 String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
61 60 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema
... ...
... ... @@ -107,6 +107,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
107 107 log.info("Schema updated.");
108 108 }
109 109 break;
  110 + case "2.0.0":
  111 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  112 + log.info("Updating schema ...");
  113 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_SQL);
  114 + loadSql(schemaUpdateFile, conn);
  115 + log.info("Schema updated.");
  116 + }
  117 + break;
  118 +
110 119 default:
111 120 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
112 121 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.service.install;
  17 +
  18 +import org.springframework.context.annotation.Profile;
  19 +import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.dao.util.SqlDao;
  21 +
  22 +@Service
  23 +@SqlDao
  24 +@Profile("install")
  25 +public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
  26 + implements EntityDatabaseSchemaService {
  27 + public SqlEntityDatabaseSchemaService() {
  28 + super("schema-entities.sql");
  29 + }
  30 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.service.install;
  17 +
  18 +import org.springframework.context.annotation.Profile;
  19 +import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.dao.util.SqlTsDao;
  21 +
  22 +@Service
  23 +@SqlTsDao
  24 +@Profile("install")
  25 +public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
  26 + implements TsDatabaseSchemaService {
  27 + public SqlTsDatabaseSchemaService() {
  28 + super("schema-ts.sql");
  29 + }
  30 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.service.install;
  17 +
  18 +public interface TsDatabaseSchemaService extends DatabaseSchemaService {
  19 +}
... ...
... ... @@ -26,6 +26,7 @@ import org.springframework.stereotype.Component;
26 26 import org.springframework.web.context.request.async.DeferredResult;
27 27 import org.thingsboard.server.common.data.Customer;
28 28 import org.thingsboard.server.common.data.Device;
  29 +import org.thingsboard.server.common.data.EntityView;
29 30 import org.thingsboard.server.common.data.Tenant;
30 31 import org.thingsboard.server.common.data.asset.Asset;
31 32 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ... @@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
34 35 import org.thingsboard.server.common.data.id.DeviceId;
35 36 import org.thingsboard.server.common.data.id.EntityId;
36 37 import org.thingsboard.server.common.data.id.EntityIdFactory;
  38 +import org.thingsboard.server.common.data.id.EntityViewId;
37 39 import org.thingsboard.server.common.data.id.RuleChainId;
38 40 import org.thingsboard.server.common.data.id.RuleNodeId;
39 41 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -44,6 +46,7 @@ import org.thingsboard.server.dao.alarm.AlarmService;
44 46 import org.thingsboard.server.dao.asset.AssetService;
45 47 import org.thingsboard.server.dao.customer.CustomerService;
46 48 import org.thingsboard.server.dao.device.DeviceService;
  49 +import org.thingsboard.server.dao.entityview.EntityViewService;
47 50 import org.thingsboard.server.dao.rule.RuleChainService;
48 51 import org.thingsboard.server.dao.tenant.TenantService;
49 52 import org.thingsboard.server.dao.user.UserService;
... ... @@ -66,6 +69,7 @@ public class AccessValidator {
66 69 public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
67 70 public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
68 71 public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
  72 + public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!";
69 73
70 74 @Autowired
71 75 protected TenantService tenantService;
... ... @@ -88,6 +92,9 @@ public class AccessValidator {
88 92 @Autowired
89 93 protected RuleChainService ruleChainService;
90 94
  95 + @Autowired
  96 + protected EntityViewService entityViewService;
  97 +
91 98 private ExecutorService executor;
92 99
93 100 @PostConstruct
... ... @@ -158,6 +165,9 @@ public class AccessValidator {
158 165 case TENANT:
159 166 validateTenant(currentUser, entityId, callback);
160 167 return;
  168 + case ENTITY_VIEW:
  169 + validateEntityView(currentUser, entityId, callback);
  170 + return;
161 171 default:
162 172 //TODO: add support of other entities
163 173 throw new IllegalStateException("Not Implemented!");
... ... @@ -293,6 +303,27 @@ public class AccessValidator {
293 303 }
294 304 }
295 305
  306 + private void validateEntityView(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  307 + if (currentUser.isSystemAdmin()) {
  308 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  309 + } else {
  310 + ListenableFuture<EntityView> entityViewFuture = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId()));
  311 + Futures.addCallback(entityViewFuture, getCallback(callback, entityView -> {
  312 + if (entityView == null) {
  313 + return ValidationResult.entityNotFound(ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND);
  314 + } else {
  315 + if (!entityView.getTenantId().equals(currentUser.getTenantId())) {
  316 + return ValidationResult.accessDenied("Entity-view doesn't belong to the current Tenant!");
  317 + } else if (currentUser.isCustomerUser() && !entityView.getCustomerId().equals(currentUser.getCustomerId())) {
  318 + return ValidationResult.accessDenied("Entity-view doesn't belong to the current Customer!");
  319 + } else {
  320 + return ValidationResult.ok(entityView);
  321 + }
  322 + }
  323 + }), executor);
  324 + }
  325 + }
  326 +
296 327 private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
297 328 return new FutureCallback<T>() {
298 329 @Override
... ...
... ... @@ -29,9 +29,11 @@ import org.thingsboard.rule.engine.api.util.DonAsynchron;
29 29 import org.thingsboard.server.actors.service.ActorService;
30 30 import org.thingsboard.server.common.data.DataConstants;
31 31 import org.thingsboard.server.common.data.EntityType;
  32 +import org.thingsboard.server.common.data.EntityView;
32 33 import org.thingsboard.server.common.data.id.DeviceId;
33 34 import org.thingsboard.server.common.data.id.EntityId;
34 35 import org.thingsboard.server.common.data.id.EntityIdFactory;
  36 +import org.thingsboard.server.common.data.id.EntityViewId;
35 37 import org.thingsboard.server.common.data.id.TenantId;
36 38 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
37 39 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
... ... @@ -48,6 +50,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
48 50 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
49 51 import org.thingsboard.server.common.msg.cluster.ServerAddress;
50 52 import org.thingsboard.server.dao.attributes.AttributesService;
  53 +import org.thingsboard.server.dao.entityview.EntityViewService;
  54 +import org.thingsboard.server.dao.model.ModelConstants;
51 55 import org.thingsboard.server.dao.timeseries.TimeseriesService;
52 56 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
53 57 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
... ... @@ -64,6 +68,7 @@ import javax.annotation.PostConstruct;
64 68 import javax.annotation.PreDestroy;
65 69 import java.util.ArrayList;
66 70 import java.util.Collections;
  71 +import java.util.HashMap;
67 72 import java.util.HashSet;
68 73 import java.util.Iterator;
69 74 import java.util.List;
... ... @@ -102,6 +107,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
102 107 private ClusterRpcService rpcService;
103 108
104 109 @Autowired
  110 + private EntityViewService entityViewService;
  111 +
  112 + @Autowired
105 113 @Lazy
106 114 private DeviceStateService stateService;
107 115
... ... @@ -133,20 +141,64 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
133 141
134 142 @Override
135 143 public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) {
  144 + long startTime = 0L;
  145 + long endTime = 0L;
  146 + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
  147 + EntityView entityView = entityViewService.findEntityViewById(new EntityViewId(entityId.getId()));
  148 + entityId = entityView.getEntityId();
  149 + startTime = entityView.getStartTimeMs();
  150 + endTime = entityView.getEndTimeMs();
  151 + sub = getUpdatedSubscriptionState(entityId, sub, entityView);
  152 + }
136 153 Optional<ServerAddress> server = routingService.resolveById(entityId);
137 154 Subscription subscription;
138 155 if (server.isPresent()) {
139 156 ServerAddress address = server.get();
140   - log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address);
141   - subscription = new Subscription(sub, true, address);
  157 + log.trace("[{}] Forwarding subscription [{}] for [{}] entity [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId, address);
  158 + subscription = new Subscription(sub, true, address, startTime, endTime);
142 159 tellNewSubscription(address, sessionId, subscription);
143 160 } else {
144   - log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId);
145   - subscription = new Subscription(sub, true);
  161 + log.trace("[{}] Registering local subscription [{}] for [{}] entity [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId);
  162 + subscription = new Subscription(sub, true, null, startTime, endTime);
146 163 }
147 164 registerSubscription(sessionId, entityId, subscription);
148 165 }
149 166
  167 + private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) {
  168 + boolean allKeys;
  169 + Map<String, Long> keyStates;
  170 + if (sub.getType().equals(TelemetryFeature.TIMESERIES) && !entityView.getKeys().getTimeseries().isEmpty()) {
  171 + allKeys = false;
  172 + keyStates = sub.getKeyStates().entrySet()
  173 + .stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey()))
  174 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  175 + } else if (sub.getType().equals(TelemetryFeature.ATTRIBUTES)) {
  176 + if (sub.getScope().equals(DataConstants.CLIENT_SCOPE) && !entityView.getKeys().getAttributes().getCs().isEmpty()) {
  177 + allKeys = false;
  178 + keyStates = filterMap(sub, entityView.getKeys().getAttributes().getCs());
  179 + } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSs().isEmpty()) {
  180 + allKeys = false;
  181 + keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSs());
  182 + } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSh().isEmpty()) {
  183 + allKeys = false;
  184 + keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSh());
  185 + } else {
  186 + allKeys = sub.isAllKeys();
  187 + keyStates = sub.getKeyStates();
  188 + }
  189 + } else {
  190 + allKeys = sub.isAllKeys();
  191 + keyStates = sub.getKeyStates();
  192 + }
  193 + return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), allKeys, keyStates, sub.getScope());
  194 + }
  195 +
  196 + private Map<String, Long> filterMap(SubscriptionState sub, List<String> allowedKeys) {
  197 + return sub.getKeyStates().entrySet()
  198 + .stream().filter(entry -> allowedKeys.contains(entry.getKey()))
  199 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  200 + }
  201 +
150 202 @Override
151 203 public void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId) {
152 204 cleanupLocalWsSessionSubscriptions(sessionId);
... ... @@ -415,7 +467,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
415 467 onLocalSubUpdate(entityId, s -> TelemetryFeature.ATTRIBUTES == s.getType() && (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope())), s -> {
416 468 List<TsKvEntry> subscriptionUpdate = null;
417 469 for (AttributeKvEntry kv : attributes) {
418   - if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
  470 + if (isInTimeRange(s, kv.getLastUpdateTs()) && (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey()))) {
419 471 if (subscriptionUpdate == null) {
420 472 subscriptionUpdate = new ArrayList<>();
421 473 }
... ... @@ -430,7 +482,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
430 482 onLocalSubUpdate(entityId, s -> TelemetryFeature.TIMESERIES == s.getType(), s -> {
431 483 List<TsKvEntry> subscriptionUpdate = null;
432 484 for (TsKvEntry kv : ts) {
433   - if (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey()))) {
  485 + if (isInTimeRange(s, kv.getTs()) && (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) {
434 486 if (subscriptionUpdate == null) {
435 487 subscriptionUpdate = new ArrayList<>();
436 488 }
... ... @@ -441,6 +493,11 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
441 493 });
442 494 }
443 495
  496 + private boolean isInTimeRange(Subscription subscription, long kvTime) {
  497 + return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime)
  498 + && (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime);
  499 + }
  500 +
444 501 private void onLocalSubUpdate(EntityId entityId, Predicate<Subscription> filter, Function<Subscription, List<TsKvEntry>> f) {
445 502 Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
446 503 if (deviceSubscriptions != null) {
... ...
... ... @@ -30,9 +30,11 @@ public class Subscription {
30 30 private final SubscriptionState sub;
31 31 private final boolean local;
32 32 private ServerAddress server;
  33 + private long startTime;
  34 + private long endTime;
33 35
34   - public Subscription(SubscriptionState sub, boolean local) {
35   - this(sub, local, null);
  36 + public Subscription(SubscriptionState sub, boolean local, ServerAddress server) {
  37 + this(sub, local, server, 0L, 0L);
36 38 }
37 39
38 40 public String getWsSessionId() {
... ...
... ... @@ -159,7 +159,11 @@ quota:
159 159 intervalMin: 2
160 160
161 161 database:
162   - type: "${DATABASE_TYPE:sql}" # cassandra OR sql
  162 + entities:
  163 + type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql
  164 + ts:
  165 + type: "${DATABASE_TS_TYPE:sql}" # cassandra OR sql (for hybrid mode, only this value should be cassandra)
  166 +
163 167
164 168 # Cassandra driver configuration parameters
165 169 cassandra:
... ... @@ -206,7 +210,7 @@ cassandra:
206 210 write_consistency_level: "${CASSANDRA_WRITE_CONSISTENCY_LEVEL:ONE}"
207 211 default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}"
208 212 # Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS,INDEFINITE
209   - ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
  213 + ts_key_value_partitioning: "${TS_KV_PARTITIONING:INDEFINITE}"
210 214 ts_key_value_ttl: "${TS_KV_TTL:0}"
211 215 buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
212 216 concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}"
... ... @@ -289,6 +293,9 @@ caffeine:
289 293 assets:
290 294 timeToLiveInMinutes: 1440
291 295 maxSize: 100000
  296 + entityViews:
  297 + timeToLiveInMinutes: 1440
  298 + maxSize: 100000
292 299
293 300 redis:
294 301 # standalone or cluster
... ... @@ -350,7 +357,7 @@ spring:
350 357 password: "${SPRING_DATASOURCE_PASSWORD:}"
351 358
352 359 # PostgreSQL DAO Configuration
353   -#spring:
  360 +# spring:
354 361 # data:
355 362 # sql:
356 363 # repositories:
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.type.TypeReference;
  20 +import org.apache.commons.lang3.RandomStringUtils;
  21 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  22 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  23 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  24 +import org.junit.After;
  25 +import org.junit.Assert;
  26 +import org.junit.Before;
  27 +import org.junit.Test;
  28 +import org.thingsboard.server.common.data.Customer;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.EntityView;
  31 +import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.User;
  33 +import org.thingsboard.server.common.data.id.CustomerId;
  34 +import org.thingsboard.server.common.data.objects.AttributesEntityView;
  35 +import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  36 +import org.thingsboard.server.common.data.page.TextPageData;
  37 +import org.thingsboard.server.common.data.page.TextPageLink;
  38 +import org.thingsboard.server.common.data.security.Authority;
  39 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  40 +import org.thingsboard.server.dao.model.ModelConstants;
  41 +
  42 +import java.util.ArrayList;
  43 +import java.util.Arrays;
  44 +import java.util.Collections;
  45 +import java.util.HashSet;
  46 +import java.util.List;
  47 +import java.util.Map;
  48 +import java.util.Set;
  49 +
  50 +import static org.hamcrest.Matchers.containsString;
  51 +import static org.junit.Assert.assertEquals;
  52 +import static org.junit.Assert.assertNotNull;
  53 +import static org.junit.Assert.assertNull;
  54 +import static org.junit.Assert.assertTrue;
  55 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  56 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
  57 +
  58 +public abstract class BaseEntityViewControllerTest extends AbstractControllerTest {
  59 +
  60 + private IdComparator<EntityView> idComparator;
  61 + private Tenant savedTenant;
  62 + private User tenantAdmin;
  63 + private Device testDevice;
  64 + private TelemetryEntityView telemetry;
  65 +
  66 + @Before
  67 + public void beforeTest() throws Exception {
  68 + loginSysAdmin();
  69 + idComparator = new IdComparator<>();
  70 +
  71 + savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class);
  72 + Assert.assertNotNull(savedTenant);
  73 +
  74 + tenantAdmin = new User();
  75 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  76 + tenantAdmin.setTenantId(savedTenant.getId());
  77 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  78 + tenantAdmin.setFirstName("Joe");
  79 + tenantAdmin.setLastName("Downs");
  80 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  81 +
  82 + Device device = new Device();
  83 + device.setName("Test device");
  84 + device.setType("default");
  85 + testDevice = doPost("/api/device", device, Device.class);
  86 +
  87 + telemetry = new TelemetryEntityView(
  88 + Arrays.asList("109", "209", "309"),
  89 + new AttributesEntityView(
  90 + Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"),
  91 + Arrays.asList("saValue1", "saValue2", "saValue3", "saValue4"),
  92 + Arrays.asList("shValue1", "shValue2", "shValue3", "shValue4")));
  93 + }
  94 +
  95 + @After
  96 + public void afterTest() throws Exception {
  97 + loginSysAdmin();
  98 +
  99 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  100 + .andExpect(status().isOk());
  101 + }
  102 +
  103 + @Test
  104 + public void testFindEntityViewById() throws Exception {
  105 + EntityView savedView = getNewSavedEntityView("Test entity view");
  106 + EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
  107 + Assert.assertNotNull(foundView);
  108 + assertEquals(savedView, foundView);
  109 + }
  110 +
  111 + @Test
  112 + public void testSaveEntityView() throws Exception {
  113 + EntityView savedView = getNewSavedEntityView("Test entity view");
  114 +
  115 + Assert.assertNotNull(savedView);
  116 + Assert.assertNotNull(savedView.getId());
  117 + Assert.assertTrue(savedView.getCreatedTime() > 0);
  118 + assertEquals(savedTenant.getId(), savedView.getTenantId());
  119 + Assert.assertNotNull(savedView.getCustomerId());
  120 + assertEquals(NULL_UUID, savedView.getCustomerId().getId());
  121 + assertEquals(savedView.getName(), savedView.getName());
  122 +
  123 + savedView.setName("New test entity view");
  124 + doPost("/api/entityView", savedView, EntityView.class);
  125 + EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
  126 +
  127 + assertEquals(foundEntityView.getName(), savedView.getName());
  128 + assertEquals(foundEntityView.getKeys(), telemetry);
  129 + }
  130 +
  131 + @Test
  132 + public void testDeleteEntityView() throws Exception {
  133 + EntityView view = getNewSavedEntityView("Test entity view");
  134 + Customer customer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class);
  135 + view.setCustomerId(customer.getId());
  136 + EntityView savedView = doPost("/api/entityView", view, EntityView.class);
  137 +
  138 + doDelete("/api/entityView/" + savedView.getId().getId().toString())
  139 + .andExpect(status().isOk());
  140 +
  141 + doGet("/api/entityView/" + savedView.getId().getId().toString())
  142 + .andExpect(status().isNotFound());
  143 + }
  144 +
  145 + @Test
  146 + public void testSaveEntityViewWithEmptyName() throws Exception {
  147 + doPost("/api/entityView", new EntityView())
  148 + .andExpect(status().isBadRequest())
  149 + .andExpect(statusReason(containsString("Entity view name should be specified!")));
  150 + }
  151 +
  152 + @Test
  153 + public void testAssignAndUnAssignedEntityViewToCustomer() throws Exception {
  154 + EntityView view = getNewSavedEntityView("Test entity view");
  155 + Customer savedCustomer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class);
  156 + view.setCustomerId(savedCustomer.getId());
  157 + EntityView savedView = doPost("/api/entityView", view, EntityView.class);
  158 +
  159 + EntityView assignedView = doPost(
  160 + "/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString(),
  161 + EntityView.class);
  162 + assertEquals(savedCustomer.getId(), assignedView.getCustomerId());
  163 +
  164 + EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
  165 + assertEquals(savedCustomer.getId(), foundView.getCustomerId());
  166 +
  167 + EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class);
  168 + assertEquals(ModelConstants.NULL_UUID, unAssignedView.getCustomerId().getId());
  169 +
  170 + foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
  171 + assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId());
  172 + }
  173 +
  174 + @Test
  175 + public void testAssignEntityViewToNonExistentCustomer() throws Exception {
  176 + EntityView savedView = getNewSavedEntityView("Test entity view");
  177 + doPost("/api/customer/" + UUIDs.timeBased().toString() + "/device/" + savedView.getId().getId().toString())
  178 + .andExpect(status().isNotFound());
  179 + }
  180 +
  181 + @Test
  182 + public void testAssignEntityViewToCustomerFromDifferentTenant() throws Exception {
  183 + loginSysAdmin();
  184 +
  185 + Tenant tenant2 = getNewTenant("Different tenant");
  186 + Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
  187 + Assert.assertNotNull(savedTenant2);
  188 +
  189 + User tenantAdmin2 = new User();
  190 + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
  191 + tenantAdmin2.setTenantId(savedTenant2.getId());
  192 + tenantAdmin2.setEmail("tenant3@thingsboard.org");
  193 + tenantAdmin2.setFirstName("Joe");
  194 + tenantAdmin2.setLastName("Downs");
  195 + createUserAndLogin(tenantAdmin2, "testPassword1");
  196 +
  197 + Customer customer = getNewCustomer("Different customer");
  198 + Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
  199 +
  200 + login(tenantAdmin.getEmail(), "testPassword1");
  201 +
  202 + EntityView savedView = getNewSavedEntityView("Test entity view");
  203 +
  204 + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString())
  205 + .andExpect(status().isForbidden());
  206 +
  207 + loginSysAdmin();
  208 +
  209 + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
  210 + .andExpect(status().isOk());
  211 + }
  212 +
  213 + @Test
  214 + public void testGetCustomerEntityViews() throws Exception {
  215 + CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId();
  216 + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?";
  217 +
  218 + List<EntityView> views = new ArrayList<>();
  219 + for (int i = 0; i < 128; i++) {
  220 + views.add(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/"
  221 + + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class));
  222 + }
  223 +
  224 + List<EntityView> loadedViews = loadListOf(new TextPageLink(23), urlTemplate);
  225 +
  226 + Collections.sort(views, idComparator);
  227 + Collections.sort(loadedViews, idComparator);
  228 +
  229 + assertEquals(views, loadedViews);
  230 + }
  231 +
  232 + @Test
  233 + public void testGetCustomerEntityViewsByName() throws Exception {
  234 + CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId();
  235 + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?";
  236 +
  237 + String name1 = "Entity view name1";
  238 + List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString()
  239 + + "/entityView/");
  240 + List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate);
  241 + Collections.sort(namesOfView1, idComparator);
  242 + Collections.sort(loadedNamesOfView1, idComparator);
  243 + assertEquals(namesOfView1, loadedNamesOfView1);
  244 +
  245 + String name2 = "Entity view name2";
  246 + List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString()
  247 + + "/entityView/");
  248 + List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate);
  249 + Collections.sort(NamesOfView2, idComparator);
  250 + Collections.sort(loadedNamesOfView2, idComparator);
  251 + assertEquals(NamesOfView2, loadedNamesOfView2);
  252 +
  253 + for (EntityView view : loadedNamesOfView1) {
  254 + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
  255 + }
  256 + TextPageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate,
  257 + new TypeReference<TextPageData<EntityView>>() {
  258 + }, new TextPageLink(4, name1));
  259 + Assert.assertFalse(pageData.hasNext());
  260 + assertEquals(0, pageData.getData().size());
  261 +
  262 + for (EntityView view : loadedNamesOfView2) {
  263 + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
  264 + }
  265 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() {
  266 + },
  267 + new TextPageLink(4, name2));
  268 + Assert.assertFalse(pageData.hasNext());
  269 + assertEquals(0, pageData.getData().size());
  270 + }
  271 +
  272 + @Test
  273 + public void testGetTenantEntityViews() throws Exception {
  274 +
  275 + List<EntityView> views = new ArrayList<>();
  276 + for (int i = 0; i < 178; i++) {
  277 + views.add(getNewSavedEntityView("Test entity view" + i));
  278 + }
  279 + List<EntityView> loadedViews = loadListOf(new TextPageLink(23), "/api/tenant/entityViews?");
  280 +
  281 + Collections.sort(views, idComparator);
  282 + Collections.sort(loadedViews, idComparator);
  283 +
  284 + assertEquals(views, loadedViews);
  285 + }
  286 +
  287 + @Test
  288 + public void testGetTenantEntityViewsByName() throws Exception {
  289 + String name1 = "Entity view name1";
  290 + List<EntityView> namesOfView1 = fillListOf(143, name1);
  291 + List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), "/api/tenant/entityViews?");
  292 + Collections.sort(namesOfView1, idComparator);
  293 + Collections.sort(loadedNamesOfView1, idComparator);
  294 + assertEquals(namesOfView1, loadedNamesOfView1);
  295 +
  296 + String name2 = "Entity view name2";
  297 + List<EntityView> NamesOfView2 = fillListOf(75, name2);
  298 + List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), "/api/tenant/entityViews?");
  299 + Collections.sort(NamesOfView2, idComparator);
  300 + Collections.sort(loadedNamesOfView2, idComparator);
  301 + assertEquals(NamesOfView2, loadedNamesOfView2);
  302 +
  303 + for (EntityView view : loadedNamesOfView1) {
  304 + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
  305 + }
  306 + TextPageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?",
  307 + new TypeReference<TextPageData<EntityView>>() {
  308 + }, new TextPageLink(4, name1));
  309 + Assert.assertFalse(pageData.hasNext());
  310 + assertEquals(0, pageData.getData().size());
  311 +
  312 + for (EntityView view : loadedNamesOfView2) {
  313 + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
  314 + }
  315 + pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference<TextPageData<EntityView>>() {
  316 + },
  317 + new TextPageLink(4, name2));
  318 + Assert.assertFalse(pageData.hasNext());
  319 + assertEquals(0, pageData.getData().size());
  320 + }
  321 +
  322 + @Test
  323 + public void testTheCopyOfAttrsIntoTSForTheView() throws Exception {
  324 + Set<String> actualAttributesSet =
  325 + getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
  326 +
  327 + Set<String> expectedActualAttributesSet =
  328 + new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
  329 + assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
  330 + Thread.sleep(1000);
  331 +
  332 + EntityView savedView = getNewSavedEntityView("Test entity view");
  333 + List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
  334 + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
  335 +
  336 + assertEquals("value1", getValue(values, "caValue1"));
  337 + assertEquals(true, getValue(values, "caValue2"));
  338 + assertEquals(42.0, getValue(values, "caValue3"));
  339 + assertEquals(73, getValue(values, "caValue4"));
  340 + }
  341 +
  342 + @Test
  343 + public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception {
  344 + Set<String> actualAttributesSet =
  345 + getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
  346 +
  347 + Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
  348 + assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
  349 + Thread.sleep(1000);
  350 +
  351 + List<Map<String, Object>> valueTelemetryOfDevices = doGetAsync("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() +
  352 + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
  353 +
  354 + EntityView view = new EntityView();
  355 + view.setEntityId(testDevice.getId());
  356 + view.setTenantId(savedTenant.getId());
  357 + view.setName("Test entity view");
  358 + view.setKeys(telemetry);
  359 + view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
  360 + view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
  361 + EntityView savedView = doPost("/api/entityView", view, EntityView.class);
  362 +
  363 + List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
  364 + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
  365 + assertEquals(0, values.size());
  366 + }
  367 +
  368 + private Set<String> getAttributesByKeys(String stringKV) throws Exception {
  369 + String viewDeviceId = testDevice.getId().getId().toString();
  370 + DeviceCredentials deviceCredentials =
  371 + doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class);
  372 + assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
  373 +
  374 + String accessToken = deviceCredentials.getCredentialsId();
  375 + assertNotNull(accessToken);
  376 +
  377 + String clientId = MqttAsyncClient.generateClientId();
  378 + MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
  379 +
  380 + MqttConnectOptions options = new MqttConnectOptions();
  381 + options.setUserName(accessToken);
  382 + client.connect(options);
  383 + Thread.sleep(3000);
  384 +
  385 + MqttMessage message = new MqttMessage();
  386 + message.setPayload((stringKV).getBytes());
  387 + client.publish("v1/devices/me/attributes", message);
  388 + Thread.sleep(1000);
  389 +
  390 + return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class));
  391 + }
  392 +
  393 + private Object getValue(List<Map<String, Object>> values, String stringValue) {
  394 + return values.size() == 0 ? null :
  395 + values.stream()
  396 + .filter(value -> value.get("key").equals(stringValue))
  397 + .findFirst().get().get("value");
  398 + }
  399 +
  400 + private EntityView getNewSavedEntityView(String name) throws Exception {
  401 + EntityView view = new EntityView();
  402 + view.setEntityId(testDevice.getId());
  403 + view.setTenantId(savedTenant.getId());
  404 + view.setName(name);
  405 + view.setKeys(telemetry);
  406 + return doPost("/api/entityView", view, EntityView.class);
  407 + }
  408 +
  409 + private Customer getNewCustomer(String title) {
  410 + Customer customer = new Customer();
  411 + customer.setTitle(title);
  412 + return customer;
  413 + }
  414 +
  415 + private Tenant getNewTenant(String title) {
  416 + Tenant tenant = new Tenant();
  417 + tenant.setTitle(title);
  418 + return tenant;
  419 + }
  420 +
  421 + private List<EntityView> fillListOf(int limit, String partOfName, String urlTemplate) throws Exception {
  422 + List<EntityView> views = new ArrayList<>();
  423 + for (EntityView view : fillListOf(limit, partOfName)) {
  424 + views.add(doPost(urlTemplate + view.getId().getId().toString(), EntityView.class));
  425 + }
  426 + return views;
  427 + }
  428 +
  429 + private List<EntityView> fillListOf(int limit, String partOfName) throws Exception {
  430 + List<EntityView> viewNames = new ArrayList<>();
  431 + for (int i = 0; i < limit; i++) {
  432 + String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15);
  433 + fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase();
  434 + EntityView view = getNewSavedEntityView(fullName);
  435 + Customer customer = getNewCustomer("Test customer " + String.valueOf(Math.random()));
  436 + view.setCustomerId(doPost("/api/customer", customer, Customer.class).getId());
  437 + viewNames.add(doPost("/api/entityView", view, EntityView.class));
  438 + }
  439 + return viewNames;
  440 + }
  441 +
  442 + private List<EntityView> loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception {
  443 + List<EntityView> loadedItems = new ArrayList<>();
  444 + TextPageData<EntityView> pageData;
  445 + do {
  446 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() {
  447 + }, pageLink);
  448 + loadedItems.addAll(pageData.getData());
  449 + if (pageData.hasNext()) {
  450 + pageLink = pageData.getNextPageLink();
  451 + }
  452 + } while (pageData.hasNext());
  453 +
  454 + return loadedItems;
  455 + }
  456 +}
... ...
... ... @@ -32,7 +32,8 @@ public class ControllerNoSqlTestSuite {
32 32 public static CustomCassandraCQLUnit cassandraUnit =
33 33 new CustomCassandraCQLUnit(
34 34 Arrays.asList(
35   - new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
  35 + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
  36 + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
36 37 new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
37 38 new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)),
38 39 "cassandra-test.yaml", 30000l);
... ...
... ... @@ -24,13 +24,13 @@ import java.util.Arrays;
24 24
25 25 @RunWith(ClasspathSuite.class)
26 26 @ClasspathSuite.ClassnameFilters({
27   - "org.thingsboard.server.controller.sql.*SqlTest",
  27 + "org.thingsboard.server.controller.sql.*Test",
28 28 })
29 29 public class ControllerSqlTestSuite {
30 30
31 31 @ClassRule
32 32 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
33   - Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
  33 + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
34 34 "sql/drop-all-tables.sql",
35 35 "sql-test.properties");
36 36 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.nosql;
  17 +
  18 +import org.thingsboard.server.controller.BaseEntityViewControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  20 +
  21 +/**
  22 + * Created by Victor Basanets on 8/27/2017.
  23 + */
  24 +@DaoNoSqlTest
  25 +public class EntityViewControllerNoSqlTest extends BaseEntityViewControllerTest {
  26 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.sql;
  17 +
  18 +import org.junit.Assert;
  19 +import org.junit.Test;
  20 +import org.thingsboard.server.common.data.EntityView;
  21 +import org.thingsboard.server.controller.BaseEntityViewControllerTest;
  22 +import org.thingsboard.server.dao.service.DaoSqlTest;
  23 +
  24 +import java.util.Arrays;
  25 +
  26 +/**
  27 + * Created by Victor Basanets on 8/27/2017.
  28 + */
  29 +@DaoSqlTest
  30 +public class EntityViewControllerSqlTest extends BaseEntityViewControllerTest {
  31 +}
... ...
... ... @@ -32,7 +32,8 @@ public class MqttNoSqlTestSuite {
32 32 public static CustomCassandraCQLUnit cassandraUnit =
33 33 new CustomCassandraCQLUnit(
34 34 Arrays.asList(
35   - new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
  35 + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
  36 + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
36 37 new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
37 38 "cassandra-test.yaml", 30000l);
38 39 }
... ...
... ... @@ -15,11 +15,9 @@
15 15 */
16 16 package org.thingsboard.server.mqtt;
17 17
18   -import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
19 18 import org.junit.ClassRule;
20 19 import org.junit.extensions.cpsuite.ClasspathSuite;
21 20 import org.junit.runner.RunWith;
22   -import org.thingsboard.server.dao.CustomCassandraCQLUnit;
23 21 import org.thingsboard.server.dao.CustomSqlUnit;
24 22
25 23 import java.util.Arrays;
... ... @@ -31,7 +29,7 @@ public class MqttSqlTestSuite {
31 29
32 30 @ClassRule
33 31 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
34   - Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
  32 + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
35 33 "sql/drop-all-tables.sql",
36 34 "sql-test.properties");
37 35 }
... ...
... ... @@ -35,7 +35,8 @@ public class RuleEngineNoSqlTestSuite {
35 35 public static CustomCassandraCQLUnit cassandraUnit =
36 36 new CustomCassandraCQLUnit(
37 37 Arrays.asList(
38   - new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
  38 + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
  39 + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
39 40 new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
40 41 "cassandra-test.yaml", 30000l);
41 42
... ...
... ... @@ -30,7 +30,7 @@ public class RuleEngineSqlTestSuite {
30 30
31 31 @ClassRule
32 32 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
33   - Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
  33 + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
34 34 "sql/drop-all-tables.sql",
35 35 "sql-test.properties");
36 36 }
... ...
... ... @@ -34,7 +34,8 @@ public class SystemNoSqlTestSuite {
34 34 public static CustomCassandraCQLUnit cassandraUnit =
35 35 new CustomCassandraCQLUnit(
36 36 Arrays.asList(
37   - new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
  37 + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
  38 + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
38 39 new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
39 40 "cassandra-test.yaml", 30000l);
40 41 }
... ...
... ... @@ -31,7 +31,7 @@ public class SystemSqlTestSuite {
31 31
32 32 @ClassRule
33 33 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
34   - Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
  34 + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
35 35 "sql/drop-all-tables.sql",
36 36 "sql-test.properties");
37 37
... ...
... ... @@ -20,4 +20,5 @@ public class CacheConstants {
20 20 public static final String RELATIONS_CACHE = "relations";
21 21 public static final String DEVICE_CACHE = "devices";
22 22 public static final String ASSET_CACHE = "assets";
  23 + public static final String ENTITY_VIEW_CACHE = "entityViews";
23 24 }
... ...
... ... @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 19 * @author Andrew Shvayka
20 20 */
21 21 public enum EntityType {
22   - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE;
  22 + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW
23 23 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.common.data;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.thingsboard.server.common.data.id.CustomerId;
  22 +import org.thingsboard.server.common.data.id.EntityId;
  23 +import org.thingsboard.server.common.data.id.EntityViewId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  26 +
  27 +/**
  28 + * Created by Victor Basanets on 8/27/2017.
  29 + */
  30 +
  31 +@Data
  32 +@AllArgsConstructor
  33 +@EqualsAndHashCode(callSuper = true)
  34 +public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
  35 + implements HasName, HasTenantId, HasCustomerId {
  36 +
  37 + private static final long serialVersionUID = 5582010124562018986L;
  38 +
  39 + private EntityId entityId;
  40 + private TenantId tenantId;
  41 + private CustomerId customerId;
  42 + private String name;
  43 + private TelemetryEntityView keys;
  44 + private long startTimeMs;
  45 + private long endTimeMs;
  46 +
  47 + public EntityView() {
  48 + super();
  49 + }
  50 +
  51 + public EntityView(EntityViewId id) {
  52 + super(id);
  53 + }
  54 +
  55 + public EntityView(EntityView entityView) {
  56 + super(entityView);
  57 + }
  58 +
  59 + @Override
  60 + public String getSearchText() {
  61 + return getName() /*What the ...*/;
  62 + }
  63 +
  64 + @Override
  65 + public CustomerId getCustomerId() {
  66 + return customerId;
  67 + }
  68 +
  69 + @Override
  70 + public String getName() {
  71 + return name;
  72 + }
  73 +
  74 + @Override
  75 + public TenantId getTenantId() {
  76 + return tenantId;
  77 + }
  78 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.common.data.entityview;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.EntityType;
  20 +import org.thingsboard.server.common.data.relation.EntityRelation;
  21 +import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
  22 +import org.thingsboard.server.common.data.relation.EntityTypeFilter;
  23 +import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
  24 +
  25 +import java.util.Collections;
  26 +import java.util.List;
  27 +
  28 +@Data
  29 +public class EntityViewSearchQuery {
  30 +
  31 + private RelationsSearchParameters parameters;
  32 + private String relationType;
  33 +
  34 + public EntityRelationsQuery toEntitySearchQuery() {
  35 + EntityRelationsQuery query = new EntityRelationsQuery();
  36 + query.setParameters(parameters);
  37 + query.setFilters(
  38 + Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
  39 + Collections.singletonList(EntityType.ENTITY_VIEW))));
  40 + return query;
  41 + }
  42 +}
... ...
... ... @@ -57,6 +57,8 @@ public class EntityIdFactory {
57 57 return new RuleChainId(uuid);
58 58 case RULE_NODE:
59 59 return new RuleNodeId(uuid);
  60 + case ENTITY_VIEW:
  61 + return new EntityViewId(uuid);
60 62 }
61 63 throw new IllegalArgumentException("EntityType " + type + " is not supported!");
62 64 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import org.thingsboard.server.common.data.EntityType;
  21 +
  22 +import java.util.UUID;
  23 +
  24 +/**
  25 + * Created by Victor Basanets on 8/27/2017.
  26 + */
  27 +public class EntityViewId extends UUIDBased implements EntityId {
  28 +
  29 + private static final long serialVersionUID = 1L;
  30 +
  31 + @JsonCreator
  32 + public EntityViewId(@JsonProperty("id") UUID id) {
  33 + super(id);
  34 + }
  35 +
  36 + public static EntityViewId fromString(String entityViewID) {
  37 + return new EntityViewId(UUID.fromString(entityViewID));
  38 + }
  39 +
  40 + @Override
  41 + public EntityType getEntityType() {
  42 + return EntityType.ENTITY_VIEW;
  43 + }
  44 +}
... ...
... ... @@ -42,4 +42,8 @@ public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
42 42 this(key, startTs, endTs, endTs - startTs, 1, Aggregation.AVG, "DESC");
43 43 }
44 44
  45 + public BaseReadTsKvQuery(String key, long startTs, long endTs, int limit, String orderBy) {
  46 + this(key, startTs, endTs, endTs - startTs, limit, Aggregation.NONE, orderBy);
  47 + }
  48 +
45 49 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.common.data.objects;
  17 +
  18 +import lombok.Data;
  19 +import lombok.NoArgsConstructor;
  20 +
  21 +import java.util.ArrayList;
  22 +import java.util.List;
  23 +
  24 +/**
  25 + * Created by Victor Basanets on 9/05/2017.
  26 + */
  27 +@Data
  28 +@NoArgsConstructor
  29 +public class AttributesEntityView {
  30 +
  31 + private List<String> cs = new ArrayList<>();
  32 + private List<String> ss = new ArrayList<>();
  33 + private List<String> sh = new ArrayList<>();
  34 +
  35 + public AttributesEntityView(List<String> cs,
  36 + List<String> ss,
  37 + List<String> sh) {
  38 +
  39 + this.cs = new ArrayList<>(cs);
  40 + this.ss = new ArrayList<>(ss);
  41 + this.sh = new ArrayList<>(sh);
  42 + }
  43 +
  44 + public AttributesEntityView(AttributesEntityView obj) {
  45 + this(obj.getCs(), obj.getSs(), obj.getSh());
  46 + }
  47 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.common.data.objects;
  17 +
  18 +import lombok.Data;
  19 +import lombok.NoArgsConstructor;
  20 +
  21 +import java.util.ArrayList;
  22 +import java.util.List;
  23 +
  24 +/**
  25 + * Created by Victor Basanets on 9/05/2017.
  26 + */
  27 +@Data
  28 +@NoArgsConstructor
  29 +public class TelemetryEntityView {
  30 +
  31 + private List<String> timeseries;
  32 + private AttributesEntityView attributes;
  33 +
  34 + public TelemetryEntityView(List<String> timeseries, AttributesEntityView attributes) {
  35 +
  36 + this.timeseries = new ArrayList<>(timeseries);
  37 + this.attributes = attributes;
  38 + }
  39 +
  40 + public TelemetryEntityView(TelemetryEntityView obj) {
  41 + this(obj.getTimeseries(), obj.getAttributes());
  42 + }
  43 +}
... ...
... ... @@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
30 30 import org.thingsboard.server.common.data.Customer;
31 31 import org.thingsboard.server.common.data.EntitySubtype;
32 32 import org.thingsboard.server.common.data.EntityType;
  33 +import org.thingsboard.server.common.data.EntityView;
33 34 import org.thingsboard.server.common.data.Tenant;
34 35 import org.thingsboard.server.common.data.asset.Asset;
35 36 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
... ... @@ -43,6 +44,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
43 44 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
44 45 import org.thingsboard.server.dao.customer.CustomerDao;
45 46 import org.thingsboard.server.dao.entity.AbstractEntityService;
  47 +import org.thingsboard.server.dao.entityview.EntityViewService;
46 48 import org.thingsboard.server.dao.exception.DataValidationException;
47 49 import org.thingsboard.server.dao.service.DataValidator;
48 50 import org.thingsboard.server.dao.service.PaginatedRemover;
... ... @@ -77,6 +79,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
77 79 private CustomerDao customerDao;
78 80
79 81 @Autowired
  82 + private EntityViewService entityViewService;
  83 +
  84 + @Autowired
80 85 private CacheManager cacheManager;
81 86
82 87 @Override
... ... @@ -130,11 +135,21 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
130 135 validateId(assetId, INCORRECT_ASSET_ID + assetId);
131 136 deleteEntityRelations(assetId);
132 137
133   - Cache cache = cacheManager.getCache(ASSET_CACHE);
134 138 Asset asset = assetDao.findById(assetId.getId());
  139 + try {
  140 + List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
  141 + if (entityViews != null && !entityViews.isEmpty()) {
  142 + throw new DataValidationException("Can't delete asset that is assigned to entity views!");
  143 + }
  144 + } catch (Exception e) {
  145 + log.error("Exception while finding entity views for assetId [{}]", assetId, e);
  146 + throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
  147 + }
  148 +
135 149 List<Object> list = new ArrayList<>();
136 150 list.add(asset.getTenantId());
137 151 list.add(asset.getName());
  152 + Cache cache = cacheManager.getCache(ASSET_CACHE);
138 153 cache.evict(list);
139 154
140 155 assetDao.removeById(assetId.getId());
... ...
... ... @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.cassandra;
17 17
18 18 import org.springframework.beans.factory.annotation.Value;
19 19 import org.springframework.stereotype.Component;
20   -import org.thingsboard.server.dao.util.NoSqlDao;
  20 +import org.thingsboard.server.dao.util.NoSqlAnyDao;
21 21
22 22 import javax.annotation.PostConstruct;
23 23
24 24 @Component
25   -@NoSqlDao
  25 +@NoSqlAnyDao
26 26 public class CassandraCluster extends AbstractCassandraCluster {
27 27
28 28 @Value("${cassandra.keyspace_name}")
... ...
... ... @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.cassandra;
17 17
18 18 import org.springframework.context.annotation.Profile;
19 19 import org.springframework.stereotype.Component;
20   -import org.thingsboard.server.dao.util.NoSqlDao;
  20 +import org.thingsboard.server.dao.util.NoSqlAnyDao;
21 21
22 22 import javax.annotation.PostConstruct;
23 23
24 24 @Component
25   -@NoSqlDao
  25 +@NoSqlAnyDao
26 26 @Profile("install")
27 27 public class CassandraInstallCluster extends AbstractCassandraCluster {
28 28
... ...
... ... @@ -21,14 +21,14 @@ import lombok.Data;
21 21 import org.springframework.beans.factory.annotation.Value;
22 22 import org.springframework.context.annotation.Configuration;
23 23 import org.springframework.stereotype.Component;
24   -import org.thingsboard.server.dao.util.NoSqlDao;
  24 +import org.thingsboard.server.dao.util.NoSqlAnyDao;
25 25
26 26 import javax.annotation.PostConstruct;
27 27
28 28 @Component
29 29 @Configuration
30 30 @Data
31   -@NoSqlDao
  31 +@NoSqlAnyDao
32 32 public class CassandraQueryOptions {
33 33
34 34 @Value("${cassandra.query.default_fetch_size}")
... ...
... ... @@ -20,14 +20,14 @@ import lombok.Data;
20 20 import org.springframework.beans.factory.annotation.Value;
21 21 import org.springframework.context.annotation.Configuration;
22 22 import org.springframework.stereotype.Component;
23   -import org.thingsboard.server.dao.util.NoSqlDao;
  23 +import org.thingsboard.server.dao.util.NoSqlAnyDao;
24 24
25 25 import javax.annotation.PostConstruct;
26 26
27 27 @Component
28 28 @Configuration
29 29 @Data
30   -@NoSqlDao
  30 +@NoSqlAnyDao
31 31 public class CassandraSocketOptions {
32 32
33 33 @Value("${cassandra.socket.connect_timeout}")
... ...
... ... @@ -32,6 +32,7 @@ import org.thingsboard.server.dao.asset.AssetService;
32 32 import org.thingsboard.server.dao.dashboard.DashboardService;
33 33 import org.thingsboard.server.dao.device.DeviceService;
34 34 import org.thingsboard.server.dao.entity.AbstractEntityService;
  35 +import org.thingsboard.server.dao.entityview.EntityViewService;
35 36 import org.thingsboard.server.dao.exception.DataValidationException;
36 37 import org.thingsboard.server.dao.exception.IncorrectParameterException;
37 38 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -70,6 +71,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
70 71 private DeviceService deviceService;
71 72
72 73 @Autowired
  74 + private EntityViewService entityViewService;
  75 +
  76 + @Autowired
73 77 private DashboardService dashboardService;
74 78
75 79 @Override
... ... @@ -111,6 +115,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
111 115 throw new IncorrectParameterException("Unable to delete non-existent customer.");
112 116 }
113 117 dashboardService.unassignCustomerDashboards(customerId);
  118 + entityViewService.unassignCustomerEntityViews(customer.getTenantId(), customerId);
114 119 assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
115 120 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
116 121 userService.deleteCustomerUsers(customer.getTenantId(), customerId);
... ...
... ... @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.Customer;
31 31 import org.thingsboard.server.common.data.Device;
32 32 import org.thingsboard.server.common.data.EntitySubtype;
33 33 import org.thingsboard.server.common.data.EntityType;
  34 +import org.thingsboard.server.common.data.EntityView;
34 35 import org.thingsboard.server.common.data.Tenant;
35 36 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
36 37 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
45 46 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
46 47 import org.thingsboard.server.dao.customer.CustomerDao;
47 48 import org.thingsboard.server.dao.entity.AbstractEntityService;
  49 +import org.thingsboard.server.dao.entityview.EntityViewService;
48 50 import org.thingsboard.server.dao.exception.DataValidationException;
49 51 import org.thingsboard.server.dao.service.DataValidator;
50 52 import org.thingsboard.server.dao.service.PaginatedRemover;
... ... @@ -87,6 +89,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
87 89 private DeviceCredentialsService deviceCredentialsService;
88 90
89 91 @Autowired
  92 + private EntityViewService entityViewService;
  93 +
  94 + @Autowired
90 95 private CacheManager cacheManager;
91 96
92 97 @Override
... ... @@ -145,18 +150,31 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
145 150 @Override
146 151 public void deleteDevice(DeviceId deviceId) {
147 152 log.trace("Executing deleteDevice [{}]", deviceId);
148   - Cache cache = cacheManager.getCache(DEVICE_CACHE);
149 153 validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
  154 +
  155 + Device device = deviceDao.findById(deviceId.getId());
  156 + try {
  157 + List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
  158 + if (entityViews != null && !entityViews.isEmpty()) {
  159 + throw new DataValidationException("Can't delete device that is assigned to entity views!");
  160 + }
  161 + } catch (Exception e) {
  162 + log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
  163 + throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e);
  164 + }
  165 +
150 166 DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId);
151 167 if (deviceCredentials != null) {
152 168 deviceCredentialsService.deleteDeviceCredentials(deviceCredentials);
153 169 }
154 170 deleteEntityRelations(deviceId);
155   - Device device = deviceDao.findById(deviceId.getId());
  171 +
156 172 List<Object> list = new ArrayList<>();
157 173 list.add(device.getTenantId());
158 174 list.add(device.getName());
  175 + Cache cache = cacheManager.getCache(DEVICE_CACHE);
159 176 cache.evict(list);
  177 +
160 178 deviceDao.removeById(deviceId.getId());
161 179 }
162 180
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.entityview;
  17 +
  18 +import com.datastax.driver.core.Statement;
  19 +import com.datastax.driver.core.querybuilder.Select;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.common.data.EntitySubtype;
  24 +import org.thingsboard.server.common.data.EntityType;
  25 +import org.thingsboard.server.common.data.EntityView;
  26 +import org.thingsboard.server.common.data.page.TextPageLink;
  27 +import org.thingsboard.server.dao.DaoUtil;
  28 +import org.thingsboard.server.dao.model.EntitySubtypeEntity;
  29 +import org.thingsboard.server.dao.model.nosql.EntityViewEntity;
  30 +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
  31 +import org.thingsboard.server.dao.util.NoSqlDao;
  32 +
  33 +import java.util.Arrays;
  34 +import java.util.Collections;
  35 +import java.util.List;
  36 +import java.util.Optional;
  37 +import java.util.UUID;
  38 +
  39 +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
  40 +import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_NAME;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
  47 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_NAME_PROPERTY;
  48 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
  49 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY;
  50 +import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY;
  51 +
  52 +/**
  53 + * Created by Victor Basanets on 9/06/2017.
  54 + */
  55 +@Component
  56 +@Slf4j
  57 +@NoSqlDao
  58 +public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<EntityViewEntity, EntityView> implements EntityViewDao {
  59 +
  60 + @Override
  61 + protected Class<EntityViewEntity> getColumnFamilyClass() {
  62 + return EntityViewEntity.class;
  63 + }
  64 +
  65 + @Override
  66 + protected String getColumnFamilyName() {
  67 + return ENTITY_VIEW_TABLE_FAMILY_NAME;
  68 + }
  69 +
  70 + @Override
  71 + public EntityView save(EntityView domain) {
  72 + EntityView savedEntityView = super.save(domain);
  73 + EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW,
  74 + savedEntityView.getId().getEntityType().toString());
  75 + EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype);
  76 + Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity);
  77 + executeWrite(saveStatement);
  78 + return savedEntityView;
  79 + }
  80 +
  81 + @Override
  82 + public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
  83 + log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
  84 + List<EntityViewEntity> entityViewEntities =
  85 + findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
  86 + Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
  87 + log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]",
  88 + entityViewEntities, tenantId, pageLink);
  89 + return DaoUtil.convertDataList(entityViewEntities);
  90 + }
  91 +
  92 + @Override
  93 + public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
  94 + Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where();
  95 + query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId));
  96 + query.and(eq(ENTITY_VIEW_NAME_PROPERTY, name));
  97 + return Optional.ofNullable(DaoUtil.getData(findOneByStatement(query)));
  98 + }
  99 +
  100 + @Override
  101 + public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
  102 + log.debug("Try to find entity views by tenantId [{}], customerId[{}] and pageLink [{}]",
  103 + tenantId, customerId, pageLink);
  104 + List<EntityViewEntity> entityViewEntities = findPageWithTextSearch(
  105 + ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF,
  106 + Arrays.asList(eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)),
  107 + pageLink);
  108 + log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}] and pageLink [{}]",
  109 + entityViewEntities, tenantId, customerId, pageLink);
  110 + return DaoUtil.convertDataList(entityViewEntities);
  111 + }
  112 +
  113 + @Override
  114 + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
  115 + log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId);
  116 + Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF).where();
  117 + query.and(eq(TENANT_ID_PROPERTY, tenantId));
  118 + query.and(eq(ENTITY_ID_COLUMN, entityId));
  119 + return findListByStatementAsync(query);
  120 + }
  121 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.entityview;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.Device;
  20 +import org.thingsboard.server.common.data.EntityView;
  21 +import org.thingsboard.server.common.data.page.TextPageLink;
  22 +import org.thingsboard.server.dao.Dao;
  23 +
  24 +import java.util.List;
  25 +import java.util.Optional;
  26 +import java.util.UUID;
  27 +
  28 +/**
  29 + * Created by Victor Basanets on 8/28/2017.
  30 + */
  31 +public interface EntityViewDao extends Dao<EntityView> {
  32 +
  33 + /**
  34 + * Save or update device object
  35 + *
  36 + * @param entityView the entity-view object
  37 + * @return saved entity-view object
  38 + */
  39 + EntityView save(EntityView entityView);
  40 +
  41 + /**
  42 + * Find entity views by tenantId and page link.
  43 + *
  44 + * @param tenantId the tenantId
  45 + * @param pageLink the page link
  46 + * @return the list of entity view objects
  47 + */
  48 + List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink);
  49 +
  50 + /**
  51 + * Find entity views by tenantId and entity view name.
  52 + *
  53 + * @param tenantId the tenantId
  54 + * @param name the entity view name
  55 + * @return the optional entity view object
  56 + */
  57 + Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name);
  58 +
  59 + /**
  60 + * Find entity views by tenantId, customerId and page link.
  61 + *
  62 + * @param tenantId the tenantId
  63 + * @param customerId the customerId
  64 + * @param pageLink the page link
  65 + * @return the list of entity view objects
  66 + */
  67 + List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId,
  68 + UUID customerId,
  69 + TextPageLink pageLink);
  70 +
  71 +
  72 + ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId);
  73 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.entityview;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.EntityView;
  20 +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
  21 +import org.thingsboard.server.common.data.id.CustomerId;
  22 +import org.thingsboard.server.common.data.id.EntityId;
  23 +import org.thingsboard.server.common.data.id.EntityViewId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.data.page.TextPageData;
  26 +import org.thingsboard.server.common.data.page.TextPageLink;
  27 +
  28 +import java.util.List;
  29 +
  30 +/**
  31 + * Created by Victor Basanets on 8/27/2017.
  32 + */
  33 +public interface EntityViewService {
  34 +
  35 + EntityView saveEntityView(EntityView entityView);
  36 +
  37 + EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId);
  38 +
  39 + EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId);
  40 +
  41 + void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId);
  42 +
  43 + EntityView findEntityViewById(EntityViewId entityViewId);
  44 +
  45 + TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
  46 +
  47 + TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
  48 +
  49 + ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
  50 +
  51 + ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
  52 +
  53 + ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId);
  54 +
  55 + void deleteEntityView(EntityViewId entityViewId);
  56 +
  57 + void deleteEntityViewsByTenantId(TenantId tenantId);
  58 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.entityview;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.apache.commons.lang3.StringUtils;
  23 +import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.springframework.cache.Cache;
  25 +import org.springframework.cache.CacheManager;
  26 +import org.springframework.cache.annotation.CacheEvict;
  27 +import org.springframework.cache.annotation.Cacheable;
  28 +import org.springframework.cache.annotation.Caching;
  29 +import org.springframework.stereotype.Service;
  30 +import org.thingsboard.server.common.data.Customer;
  31 +import org.thingsboard.server.common.data.DataConstants;
  32 +import org.thingsboard.server.common.data.EntityType;
  33 +import org.thingsboard.server.common.data.EntityView;
  34 +import org.thingsboard.server.common.data.Tenant;
  35 +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
  36 +import org.thingsboard.server.common.data.id.CustomerId;
  37 +import org.thingsboard.server.common.data.id.EntityId;
  38 +import org.thingsboard.server.common.data.id.EntityViewId;
  39 +import org.thingsboard.server.common.data.id.TenantId;
  40 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  41 +import org.thingsboard.server.common.data.page.TextPageData;
  42 +import org.thingsboard.server.common.data.page.TextPageLink;
  43 +import org.thingsboard.server.common.data.relation.EntityRelation;
  44 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  45 +import org.thingsboard.server.dao.attributes.AttributesService;
  46 +import org.thingsboard.server.dao.customer.CustomerDao;
  47 +import org.thingsboard.server.dao.entity.AbstractEntityService;
  48 +import org.thingsboard.server.dao.exception.DataValidationException;
  49 +import org.thingsboard.server.dao.service.DataValidator;
  50 +import org.thingsboard.server.dao.service.PaginatedRemover;
  51 +import org.thingsboard.server.dao.tenant.TenantDao;
  52 +
  53 +import javax.annotation.Nullable;
  54 +import java.util.ArrayList;
  55 +import java.util.Arrays;
  56 +import java.util.Collection;
  57 +import java.util.List;
  58 +import java.util.concurrent.ExecutionException;
  59 +import java.util.stream.Collectors;
  60 +
  61 +import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE;
  62 +import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
  63 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
  64 +import static org.thingsboard.server.dao.service.Validator.validateId;
  65 +import static org.thingsboard.server.dao.service.Validator.validatePageLink;
  66 +
  67 +/**
  68 + * Created by Victor Basanets on 8/28/2017.
  69 + */
  70 +@Service
  71 +@Slf4j
  72 +public class EntityViewServiceImpl extends AbstractEntityService implements EntityViewService {
  73 +
  74 + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  75 + public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
  76 + public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
  77 + public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId ";
  78 +
  79 + @Autowired
  80 + private EntityViewDao entityViewDao;
  81 +
  82 + @Autowired
  83 + private TenantDao tenantDao;
  84 +
  85 + @Autowired
  86 + private CustomerDao customerDao;
  87 +
  88 + @Autowired
  89 + private AttributesService attributesService;
  90 +
  91 + @Autowired
  92 + private CacheManager cacheManager;
  93 +
  94 + @Caching(evict = {
  95 + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"),
  96 + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")})
  97 + @Override
  98 + public EntityView saveEntityView(EntityView entityView) {
  99 + log.trace("Executing save entity view [{}]", entityView);
  100 + entityViewValidator.validate(entityView);
  101 + EntityView savedEntityView = entityViewDao.save(entityView);
  102 +
  103 + List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
  104 + if (savedEntityView.getKeys() != null) {
  105 + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs()));
  106 + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs()));
  107 + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh()));
  108 + }
  109 + for (ListenableFuture<List<Void>> future : futures) {
  110 + try {
  111 + future.get();
  112 + } catch (InterruptedException | ExecutionException e) {
  113 + log.error("Failed to copy attributes to entity view", e);
  114 + throw new RuntimeException("Failed to copy attributes to entity view", e);
  115 + }
  116 + }
  117 + return savedEntityView;
  118 + }
  119 +
  120 + @Override
  121 + public EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId) {
  122 + EntityView entityView = findEntityViewById(entityViewId);
  123 + entityView.setCustomerId(customerId);
  124 + return saveEntityView(entityView);
  125 + }
  126 +
  127 + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
  128 + @Override
  129 + public EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId) {
  130 + EntityView entityView = findEntityViewById(entityViewId);
  131 + entityView.setCustomerId(null);
  132 + return saveEntityView(entityView);
  133 + }
  134 +
  135 + @Override
  136 + public void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId) {
  137 + log.trace("Executing unassignCustomerEntityViews, tenantId [{}], customerId [{}]", tenantId, customerId);
  138 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  139 + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
  140 + new CustomerEntityViewsUnAssigner(tenantId).removeEntities(customerId);
  141 + }
  142 +
  143 + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
  144 + @Override
  145 + public EntityView findEntityViewById(EntityViewId entityViewId) {
  146 + log.trace("Executing findEntityViewById [{}]", entityViewId);
  147 + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
  148 + return entityViewDao.findById(entityViewId.getId());
  149 + }
  150 +
  151 + @Override
  152 + public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) {
  153 + log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
  154 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  155 + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
  156 + List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink);
  157 + return new TextPageData<>(entityViews, pageLink);
  158 + }
  159 +
  160 + @Override
  161 + public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
  162 + TextPageLink pageLink) {
  163 + log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," +
  164 + " pageLink [{}]", tenantId, customerId, pageLink);
  165 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  166 + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
  167 + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
  168 + List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(),
  169 + customerId.getId(), pageLink);
  170 + return new TextPageData<>(entityViews, pageLink);
  171 + }
  172 +
  173 + @Override
  174 + public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
  175 + ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
  176 + ListenableFuture<List<EntityView>> entityViews = Futures.transformAsync(relations, r -> {
  177 + EntitySearchDirection direction = query.toEntitySearchQuery().getParameters().getDirection();
  178 + List<ListenableFuture<EntityView>> futures = new ArrayList<>();
  179 + for (EntityRelation relation : r) {
  180 + EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom();
  181 + if (entityId.getEntityType() == EntityType.ENTITY_VIEW) {
  182 + futures.add(findEntityViewByIdAsync(new EntityViewId(entityId.getId())));
  183 + }
  184 + }
  185 + return Futures.successfulAsList(futures);
  186 + });
  187 + return entityViews;
  188 + }
  189 +
  190 + @Override
  191 + public ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId) {
  192 + log.trace("Executing findEntityViewById [{}]", entityViewId);
  193 + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
  194 + return entityViewDao.findByIdAsync(entityViewId.getId());
  195 + }
  196 +
  197 + @Override
  198 + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) {
  199 + log.trace("Executing findEntityViewsByTenantIdAndEntityIdAsync, tenantId [{}], entityId [{}]", tenantId, entityId);
  200 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  201 + validateId(entityId.getId(), "Incorrect entityId" + entityId);
  202 +
  203 + List<Object> tenantIdAndEntityId = new ArrayList<>();
  204 + tenantIdAndEntityId.add(tenantId);
  205 + tenantIdAndEntityId.add(entityId);
  206 +
  207 + Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE);
  208 + List<EntityView> fromCache = cache.get(tenantIdAndEntityId, List.class);
  209 + if (fromCache != null) {
  210 + return Futures.immediateFuture(fromCache);
  211 + } else {
  212 + ListenableFuture<List<EntityView>> entityViewsFuture = entityViewDao.findEntityViewsByTenantIdAndEntityIdAsync(tenantId.getId(), entityId.getId());
  213 + Futures.addCallback(entityViewsFuture,
  214 + new FutureCallback<List<EntityView>>() {
  215 + @Override
  216 + public void onSuccess(@Nullable List<EntityView> result) {
  217 + cache.putIfAbsent(tenantIdAndEntityId, result);
  218 + }
  219 + @Override
  220 + public void onFailure(Throwable t) {
  221 + log.error("Error while finding entity views by tenantId and entityId", t);
  222 + }
  223 + });
  224 + return entityViewsFuture;
  225 + }
  226 + }
  227 +
  228 + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
  229 + @Override
  230 + public void deleteEntityView(EntityViewId entityViewId) {
  231 + log.trace("Executing deleteEntityView [{}]", entityViewId);
  232 + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
  233 + deleteEntityRelations(entityViewId);
  234 + EntityView entityView = entityViewDao.findById(entityViewId.getId());
  235 + cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId()));
  236 + entityViewDao.removeById(entityViewId.getId());
  237 + }
  238 +
  239 + @Override
  240 + public void deleteEntityViewsByTenantId(TenantId tenantId) {
  241 + log.trace("Executing deleteEntityViewsByTenantId, tenantId [{}]", tenantId);
  242 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  243 + tenantEntityViewRemover.removeEntities(tenantId);
  244 + }
  245 +
  246 + private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
  247 + if (keys != null && !keys.isEmpty()) {
  248 + ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
  249 + return Futures.transform(getAttrFuture, attributeKvEntries -> {
  250 + List<AttributeKvEntry> filteredAttributes = new ArrayList<>();
  251 + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
  252 + filteredAttributes =
  253 + attributeKvEntries.stream()
  254 + .filter(attributeKvEntry -> {
  255 + long startTime = entityView.getStartTimeMs();
  256 + long endTime = entityView.getEndTimeMs();
  257 + long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
  258 + return startTime == 0 && endTime == 0 ||
  259 + (endTime == 0 && startTime < lastUpdateTs) ||
  260 + (startTime == 0 && endTime > lastUpdateTs)
  261 + ? true : startTime < lastUpdateTs && endTime > lastUpdateTs;
  262 + }).collect(Collectors.toList());
  263 + }
  264 + try {
  265 + return attributesService.save(entityView.getId(), scope, filteredAttributes).get();
  266 + } catch (InterruptedException | ExecutionException e) {
  267 + log.error("Failed to copy attributes to entity view", e);
  268 + throw new RuntimeException("Failed to copy attributes to entity view", e);
  269 + }
  270 + });
  271 + } else {
  272 + return Futures.immediateFuture(null);
  273 + }
  274 + }
  275 +
  276 + private DataValidator<EntityView> entityViewValidator =
  277 + new DataValidator<EntityView>() {
  278 +
  279 + @Override
  280 + protected void validateCreate(EntityView entityView) {
  281 + entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
  282 + .ifPresent(e -> {
  283 + throw new DataValidationException("Entity view with such name already exists!");
  284 + });
  285 + }
  286 +
  287 + @Override
  288 + protected void validateUpdate(EntityView entityView) {
  289 + entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
  290 + .ifPresent(e -> {
  291 + if (!e.getUuidId().equals(entityView.getUuidId())) {
  292 + throw new DataValidationException("Entity view with such name already exists!");
  293 + }
  294 + });
  295 + }
  296 +
  297 + @Override
  298 + protected void validateDataImpl(EntityView entityView) {
  299 + if (StringUtils.isEmpty(entityView.getName())) {
  300 + throw new DataValidationException("Entity view name should be specified!");
  301 + }
  302 + if (entityView.getTenantId() == null) {
  303 + throw new DataValidationException("Entity view should be assigned to tenant!");
  304 + } else {
  305 + Tenant tenant = tenantDao.findById(entityView.getTenantId().getId());
  306 + if (tenant == null) {
  307 + throw new DataValidationException("Entity view is referencing to non-existent tenant!");
  308 + }
  309 + }
  310 + if (entityView.getCustomerId() == null) {
  311 + entityView.setCustomerId(new CustomerId(NULL_UUID));
  312 + } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) {
  313 + Customer customer = customerDao.findById(entityView.getCustomerId().getId());
  314 + if (customer == null) {
  315 + throw new DataValidationException("Can't assign entity view to non-existent customer!");
  316 + }
  317 + if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) {
  318 + throw new DataValidationException("Can't assign entity view to customer from different tenant!");
  319 + }
  320 + }
  321 + }
  322 + };
  323 +
  324 + private PaginatedRemover<TenantId, EntityView> tenantEntityViewRemover =
  325 + new PaginatedRemover<TenantId, EntityView>() {
  326 +
  327 + @Override
  328 + protected List<EntityView> findEntities(TenantId id, TextPageLink pageLink) {
  329 + return entityViewDao.findEntityViewsByTenantId(id.getId(), pageLink);
  330 + }
  331 +
  332 + @Override
  333 + protected void removeEntity(EntityView entity) {
  334 + deleteEntityView(new EntityViewId(entity.getUuidId()));
  335 + }
  336 + };
  337 +
  338 + private class CustomerEntityViewsUnAssigner extends PaginatedRemover<CustomerId, EntityView> {
  339 +
  340 + private TenantId tenantId;
  341 +
  342 + CustomerEntityViewsUnAssigner(TenantId tenantId) {
  343 + this.tenantId = tenantId;
  344 + }
  345 +
  346 + @Override
  347 + protected List<EntityView> findEntities(CustomerId id, TextPageLink pageLink) {
  348 + return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
  349 + }
  350 +
  351 + @Override
  352 + protected void removeEntity(EntityView entity) {
  353 + unassignEntityViewFromCustomer(new EntityViewId(entity.getUuidId()));
  354 + }
  355 + }
  356 +}
... ...
... ... @@ -52,7 +52,6 @@ public class ModelConstants {
52 52 public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key";
53 53 public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts";
54 54
55   -
56 55 /**
57 56 * Cassandra user constants.
58 57 */
... ... @@ -130,12 +129,12 @@ public class ModelConstants {
130 129 * Cassandra device constants.
131 130 */
132 131 public static final String DEVICE_COLUMN_FAMILY_NAME = "device";
  132 + public static final String DEVICE_FAMILY_NAME = "device";
133 133 public static final String DEVICE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
134 134 public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
135 135 public static final String DEVICE_NAME_PROPERTY = "name";
136 136 public static final String DEVICE_TYPE_PROPERTY = "type";
137 137 public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
138   -
139 138 public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
140 139 public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
141 140 public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
... ... @@ -144,6 +143,23 @@ public class ModelConstants {
144 143 public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant";
145 144
146 145 /**
  146 + * Cassandra entityView constants.
  147 + */
  148 + public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views";
  149 + public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
  150 + public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
  151 + public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
  152 + public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY;
  153 + public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer";
  154 + public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id";
  155 + public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
  156 + public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts";
  157 + public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts";
  158 + public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
  159 + public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text";
  160 + public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
  161 +
  162 + /**
147 163 * Cassandra audit log constants.
148 164 */
149 165 public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log";
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.nosql;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.datastax.driver.mapping.annotations.Column;
  20 +import com.datastax.driver.mapping.annotations.PartitionKey;
  21 +import com.datastax.driver.mapping.annotations.Table;
  22 +import com.fasterxml.jackson.databind.JsonNode;
  23 +import com.fasterxml.jackson.databind.ObjectMapper;
  24 +import lombok.Data;
  25 +import lombok.EqualsAndHashCode;
  26 +import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
  28 +import org.hibernate.annotations.Type;
  29 +import org.thingsboard.server.common.data.EntityType;
  30 +import org.thingsboard.server.common.data.EntityView;
  31 +import org.thingsboard.server.common.data.id.CustomerId;
  32 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  33 +import org.thingsboard.server.common.data.id.EntityViewId;
  34 +import org.thingsboard.server.common.data.id.TenantId;
  35 +import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  36 +import org.thingsboard.server.dao.model.ModelConstants;
  37 +import org.thingsboard.server.dao.model.SearchTextEntity;
  38 +
  39 +import javax.persistence.EnumType;
  40 +import javax.persistence.Enumerated;
  41 +import java.io.IOException;
  42 +import java.util.UUID;
  43 +
  44 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
  47 +
  48 +/**
  49 + * Created by Victor Basanets on 8/31/2017.
  50 + */
  51 +@Data
  52 +@Table(name = ENTITY_VIEW_TABLE_FAMILY_NAME)
  53 +@EqualsAndHashCode
  54 +@ToString
  55 +@Slf4j
  56 +public class EntityViewEntity implements SearchTextEntity<EntityView> {
  57 +
  58 + @PartitionKey(value = 0)
  59 + @Column(name = ID_PROPERTY)
  60 + private UUID id;
  61 +
  62 + @Enumerated(EnumType.STRING)
  63 + @Column(name = ENTITY_TYPE_PROPERTY)
  64 + private EntityType entityType;
  65 +
  66 + @PartitionKey(value = 1)
  67 + @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY)
  68 + private UUID tenantId;
  69 +
  70 + @PartitionKey(value = 2)
  71 + @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
  72 + private UUID customerId;
  73 +
  74 + @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
  75 + private UUID entityId;
  76 +
  77 + @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
  78 + private String name;
  79 +
  80 + @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY)
  81 + private String keys;
  82 +
  83 + @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY)
  84 + private long startTs;
  85 +
  86 + @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY)
  87 + private long endTs;
  88 +
  89 + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
  90 + private String searchText;
  91 +
  92 + @Type(type = "json")
  93 + @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY)
  94 + private JsonNode additionalInfo;
  95 +
  96 + private static final ObjectMapper mapper = new ObjectMapper();
  97 +
  98 + public EntityViewEntity() {
  99 + super();
  100 + }
  101 +
  102 + public EntityViewEntity(EntityView entityView) {
  103 + if (entityView.getId() != null) {
  104 + this.id = entityView.getId().getId();
  105 + }
  106 + if (entityView.getEntityId() != null) {
  107 + this.entityId = entityView.getEntityId().getId();
  108 + this.entityType = entityView.getEntityId().getEntityType();
  109 + }
  110 + if (entityView.getTenantId() != null) {
  111 + this.tenantId = entityView.getTenantId().getId();
  112 + }
  113 + if (entityView.getCustomerId() != null) {
  114 + this.customerId = entityView.getCustomerId().getId();
  115 + }
  116 + this.name = entityView.getName();
  117 + try {
  118 + this.keys = mapper.writeValueAsString(entityView.getKeys());
  119 + } catch (IOException e) {
  120 + log.error("Unable to serialize entity view keys!", e);
  121 + }
  122 + this.startTs = entityView.getStartTimeMs();
  123 + this.endTs = entityView.getEndTimeMs();
  124 + this.searchText = entityView.getSearchText();
  125 + this.additionalInfo = entityView.getAdditionalInfo();
  126 + }
  127 +
  128 + @Override
  129 + public String getSearchTextSource() {
  130 + return name;
  131 + }
  132 +
  133 + @Override
  134 + public EntityView toData() {
  135 + EntityView entityView = new EntityView(new EntityViewId(id));
  136 + entityView.setCreatedTime(UUIDs.unixTimestamp(id));
  137 + if (entityId != null) {
  138 + entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), entityId.toString()));
  139 + }
  140 + if (tenantId != null) {
  141 + entityView.setTenantId(new TenantId(tenantId));
  142 + }
  143 + if (customerId != null) {
  144 + entityView.setCustomerId(new CustomerId(customerId));
  145 + }
  146 + entityView.setName(name);
  147 + try {
  148 + entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
  149 + } catch (IOException e) {
  150 + log.error("Unable to read entity view keys!", e);
  151 + }
  152 + entityView.setStartTimeMs(startTs);
  153 + entityView.setEndTimeMs(endTs);
  154 + entityView.setAdditionalInfo(additionalInfo);
  155 + return entityView;
  156 + }
  157 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.sql;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import lombok.Data;
  22 +import lombok.EqualsAndHashCode;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.hibernate.annotations.Type;
  25 +import org.hibernate.annotations.TypeDef;
  26 +import org.thingsboard.server.common.data.EntityType;
  27 +import org.thingsboard.server.common.data.EntityView;
  28 +import org.thingsboard.server.common.data.id.CustomerId;
  29 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  30 +import org.thingsboard.server.common.data.id.EntityViewId;
  31 +import org.thingsboard.server.common.data.id.TenantId;
  32 +import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  33 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  34 +import org.thingsboard.server.dao.model.ModelConstants;
  35 +import org.thingsboard.server.dao.model.SearchTextEntity;
  36 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  37 +
  38 +import javax.persistence.Column;
  39 +import javax.persistence.Entity;
  40 +import javax.persistence.EnumType;
  41 +import javax.persistence.Enumerated;
  42 +import javax.persistence.Table;
  43 +import java.io.IOException;
  44 +
  45 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
  46 +
  47 +/**
  48 + * Created by Victor Basanets on 8/30/2017.
  49 + */
  50 +
  51 +@Data
  52 +@EqualsAndHashCode(callSuper = true)
  53 +@Entity
  54 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  55 +@Table(name = ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME)
  56 +@Slf4j
  57 +public class EntityViewEntity extends BaseSqlEntity<EntityView> implements SearchTextEntity<EntityView> {
  58 +
  59 + @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
  60 + private String entityId;
  61 +
  62 + @Enumerated(EnumType.STRING)
  63 + @Column(name = ENTITY_TYPE_PROPERTY)
  64 + private EntityType entityType;
  65 +
  66 + @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY)
  67 + private String tenantId;
  68 +
  69 + @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
  70 + private String customerId;
  71 +
  72 + @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
  73 + private String name;
  74 +
  75 + @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY)
  76 + private String keys;
  77 +
  78 + @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY)
  79 + private long startTs;
  80 +
  81 + @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY)
  82 + private long endTs;
  83 +
  84 + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
  85 + private String searchText;
  86 +
  87 + @Type(type = "json")
  88 + @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY)
  89 + private JsonNode additionalInfo;
  90 +
  91 + private static final ObjectMapper mapper = new ObjectMapper();
  92 +
  93 + public EntityViewEntity() {
  94 + super();
  95 + }
  96 +
  97 + public EntityViewEntity(EntityView entityView) {
  98 + if (entityView.getId() != null) {
  99 + this.setId(entityView.getId().getId());
  100 + }
  101 + if (entityView.getEntityId() != null) {
  102 + this.entityId = toString(entityView.getEntityId().getId());
  103 + this.entityType = entityView.getEntityId().getEntityType();
  104 + }
  105 + if (entityView.getTenantId() != null) {
  106 + this.tenantId = toString(entityView.getTenantId().getId());
  107 + }
  108 + if (entityView.getCustomerId() != null) {
  109 + this.customerId = toString(entityView.getCustomerId().getId());
  110 + }
  111 + this.name = entityView.getName();
  112 + try {
  113 + this.keys = mapper.writeValueAsString(entityView.getKeys());
  114 + } catch (IOException e) {
  115 + log.error("Unable to serialize entity view keys!", e);
  116 + }
  117 + this.startTs = entityView.getStartTimeMs();
  118 + this.endTs = entityView.getEndTimeMs();
  119 + this.searchText = entityView.getSearchText();
  120 + this.additionalInfo = entityView.getAdditionalInfo();
  121 + }
  122 +
  123 + @Override
  124 + public String getSearchTextSource() {
  125 + return name;
  126 + }
  127 +
  128 + @Override
  129 + public void setSearchText(String searchText) {
  130 + this.searchText = searchText;
  131 + }
  132 +
  133 + @Override
  134 + public EntityView toData() {
  135 + EntityView entityView = new EntityView(new EntityViewId(getId()));
  136 + entityView.setCreatedTime(UUIDs.unixTimestamp(getId()));
  137 +
  138 + if (entityId != null) {
  139 + entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString()));
  140 + }
  141 + if (tenantId != null) {
  142 + entityView.setTenantId(new TenantId(toUUID(tenantId)));
  143 + }
  144 + if (customerId != null) {
  145 + entityView.setCustomerId(new CustomerId(toUUID(customerId)));
  146 + }
  147 + entityView.setName(name);
  148 + try {
  149 + entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
  150 + } catch (IOException e) {
  151 + log.error("Unable to read entity view keys!", e);
  152 + }
  153 + entityView.setStartTimeMs(startTs);
  154 + entityView.setEndTimeMs(endTs);
  155 + entityView.setAdditionalInfo(additionalInfo);
  156 + return entityView;
  157 + }
  158 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.sql.entityview;
  17 +
  18 +import org.springframework.data.domain.Pageable;
  19 +import org.springframework.data.jpa.repository.Query;
  20 +import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.data.repository.query.Param;
  22 +import org.thingsboard.server.common.data.EntityView;
  23 +import org.thingsboard.server.common.data.id.EntityId;
  24 +import org.thingsboard.server.dao.model.sql.EntityViewEntity;
  25 +import org.thingsboard.server.dao.util.SqlDao;
  26 +
  27 +import java.util.List;
  28 +
  29 +/**
  30 + * Created by Victor Basanets on 8/31/2017.
  31 + */
  32 +@SqlDao
  33 +public interface EntityViewRepository extends CrudRepository<EntityViewEntity, String> {
  34 +
  35 + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
  36 + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
  37 + "AND e.id > :idOffset ORDER BY e.id")
  38 + List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
  39 + @Param("textSearch") String textSearch,
  40 + @Param("idOffset") String idOffset,
  41 + Pageable pageable);
  42 +
  43 + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
  44 + "AND e.customerId = :customerId " +
  45 + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
  46 + "AND e.id > :idOffset ORDER BY e.id")
  47 + List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
  48 + @Param("customerId") String customerId,
  49 + @Param("searchText") String searchText,
  50 + @Param("idOffset") String idOffset,
  51 + Pageable pageable);
  52 +
  53 + EntityViewEntity findByTenantIdAndName(String tenantId, String name);
  54 +
  55 + List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId);
  56 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.sql.entityview;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.data.domain.PageRequest;
  21 +import org.springframework.data.repository.CrudRepository;
  22 +import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.common.data.EntitySubtype;
  24 +import org.thingsboard.server.common.data.EntityType;
  25 +import org.thingsboard.server.common.data.EntityView;
  26 +import org.thingsboard.server.common.data.UUIDConverter;
  27 +import org.thingsboard.server.common.data.id.EntityId;
  28 +import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.page.TextPageLink;
  30 +import org.thingsboard.server.dao.DaoUtil;
  31 +import org.thingsboard.server.dao.entityview.EntityViewDao;
  32 +import org.thingsboard.server.dao.model.sql.EntityViewEntity;
  33 +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
  34 +import org.thingsboard.server.dao.util.SqlDao;
  35 +
  36 +import java.util.ArrayList;
  37 +import java.util.Collections;
  38 +import java.util.List;
  39 +import java.util.Objects;
  40 +import java.util.Optional;
  41 +import java.util.UUID;
  42 +
  43 +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
  44 +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
  46 +
  47 +/**
  48 + * Created by Victor Basanets on 8/31/2017.
  49 + */
  50 +@Component
  51 +@SqlDao
  52 +public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity, EntityView>
  53 + implements EntityViewDao {
  54 +
  55 + @Autowired
  56 + private EntityViewRepository entityViewRepository;
  57 +
  58 + @Override
  59 + protected Class<EntityViewEntity> getEntityClass() {
  60 + return EntityViewEntity.class;
  61 + }
  62 +
  63 + @Override
  64 + protected CrudRepository<EntityViewEntity, String> getCrudRepository() {
  65 + return entityViewRepository;
  66 + }
  67 +
  68 + @Override
  69 + public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
  70 + return DaoUtil.convertDataList(
  71 + entityViewRepository.findByTenantId(
  72 + fromTimeUUID(tenantId),
  73 + Objects.toString(pageLink.getTextSearch(), ""),
  74 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  75 + new PageRequest(0, pageLink.getLimit())));
  76 + }
  77 +
  78 + @Override
  79 + public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
  80 + return Optional.ofNullable(
  81 + DaoUtil.getData(entityViewRepository.findByTenantIdAndName(fromTimeUUID(tenantId), name)));
  82 + }
  83 +
  84 + @Override
  85 + public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId,
  86 + UUID customerId,
  87 + TextPageLink pageLink) {
  88 + return DaoUtil.convertDataList(
  89 + entityViewRepository.findByTenantIdAndCustomerId(
  90 + fromTimeUUID(tenantId),
  91 + fromTimeUUID(customerId),
  92 + Objects.toString(pageLink.getTextSearch(), ""),
  93 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  94 + new PageRequest(0, pageLink.getLimit())
  95 + ));
  96 + }
  97 +
  98 + @Override
  99 + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
  100 + return service.submit(() -> DaoUtil.convertDataList(
  101 + entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId))));
  102 + }
  103 +}
... ...
... ... @@ -44,6 +44,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
44 44 import org.thingsboard.server.dao.timeseries.TimeseriesDao;
45 45 import org.thingsboard.server.dao.timeseries.TsInsertExecutorType;
46 46 import org.thingsboard.server.dao.util.SqlDao;
  47 +import org.thingsboard.server.dao.util.SqlTsDao;
47 48
48 49 import javax.annotation.Nullable;
49 50 import javax.annotation.PostConstruct;
... ... @@ -60,7 +61,7 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
60 61
61 62 @Component
62 63 @Slf4j
63   -@SqlDao
  64 +@SqlTsDao
64 65 public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao {
65 66
66 67 @Value("${sql.ts_inserts_executor_type}")
... ...
... ... @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
29 29 import org.thingsboard.server.dao.dashboard.DashboardService;
30 30 import org.thingsboard.server.dao.device.DeviceService;
31 31 import org.thingsboard.server.dao.entity.AbstractEntityService;
  32 +import org.thingsboard.server.dao.entityview.EntityViewService;
32 33 import org.thingsboard.server.dao.exception.DataValidationException;
33 34 import org.thingsboard.server.dao.rule.RuleChainService;
34 35 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -64,6 +65,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
64 65 private DeviceService deviceService;
65 66
66 67 @Autowired
  68 + private EntityViewService entityViewService;
  69 +
  70 + @Autowired
67 71 private WidgetsBundleService widgetsBundleService;
68 72
69 73 @Autowired
... ... @@ -101,6 +105,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
101 105 customerService.deleteCustomersByTenantId(tenantId);
102 106 widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
103 107 dashboardService.deleteDashboardsByTenantId(tenantId);
  108 + entityViewService.deleteEntityViewsByTenantId(tenantId);
104 109 assetService.deleteAssetsByTenantId(tenantId);
105 110 deviceService.deleteDevicesByTenantId(tenantId);
106 111 userService.deleteTenantAdmins(tenantId);
... ...
... ... @@ -21,15 +21,22 @@ import com.google.common.util.concurrent.ListenableFuture;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.springframework.beans.factory.annotation.Autowired;
23 23 import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.common.data.EntityType;
  25 +import org.thingsboard.server.common.data.EntityView;
24 26 import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.EntityViewId;
  28 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
25 29 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
26 30 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
27 31 import org.thingsboard.server.common.data.kv.TsKvEntry;
  32 +import org.thingsboard.server.dao.entityview.EntityViewService;
28 33 import org.thingsboard.server.dao.exception.IncorrectParameterException;
29 34 import org.thingsboard.server.dao.service.Validator;
30 35
  36 +import java.util.ArrayList;
31 37 import java.util.Collection;
32 38 import java.util.List;
  39 +import java.util.stream.Collectors;
33 40
34 41 import static org.apache.commons.lang3.StringUtils.isBlank;
35 42
... ... @@ -46,10 +53,21 @@ public class BaseTimeseriesService implements TimeseriesService {
46 53 @Autowired
47 54 private TimeseriesDao timeseriesDao;
48 55
  56 + @Autowired
  57 + private EntityViewService entityViewService;
  58 +
49 59 @Override
50 60 public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) {
51 61 validate(entityId);
52 62 queries.forEach(BaseTimeseriesService::validate);
  63 + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
  64 + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
  65 + List<ReadTsKvQuery> filteredQueries =
  66 + queries.stream()
  67 + .filter(query -> entityView.getKeys().getTimeseries().isEmpty() || entityView.getKeys().getTimeseries().contains(query.getKey()))
  68 + .collect(Collectors.toList());
  69 + return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, filteredQueries));
  70 + }
53 71 return timeseriesDao.findAllAsync(entityId, queries);
54 72 }
55 73
... ... @@ -58,6 +76,19 @@ public class BaseTimeseriesService implements TimeseriesService {
58 76 validate(entityId);
59 77 List<ListenableFuture<TsKvEntry>> futures = Lists.newArrayListWithExpectedSize(keys.size());
60 78 keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key));
  79 + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
  80 + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
  81 + List<String> filteredKeys = new ArrayList<>(keys);
  82 + if (!entityView.getKeys().getTimeseries().isEmpty()) {
  83 + filteredKeys.retainAll(entityView.getKeys().getTimeseries());
  84 + }
  85 + List<ReadTsKvQuery> queries =
  86 + filteredKeys.stream()
  87 + .map(key -> new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), entityView.getEndTimeMs(), 1, "ASC"))
  88 + .collect(Collectors.toList());
  89 +
  90 + return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
  91 + }
61 92 keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
62 93 return Futures.allAsList(futures);
63 94 }
... ... @@ -92,11 +123,24 @@ public class BaseTimeseriesService implements TimeseriesService {
92 123 }
93 124
94 125 private void saveAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) {
  126 + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
  127 + throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Only read only");
  128 + }
95 129 futures.add(timeseriesDao.savePartition(entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl));
96 130 futures.add(timeseriesDao.saveLatest(entityId, tsKvEntry));
97 131 futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl));
98 132 }
99 133
  134 + private List<ReadTsKvQuery> updateQueriesForEntityView(EntityView entityView, List<ReadTsKvQuery> queries) {
  135 + return queries.stream().map(query -> {
  136 + long startTs = entityView.getStartTimeMs() == 0 ? query.getStartTs() : entityView.getStartTimeMs();
  137 + long endTs = entityView.getEndTimeMs() == 0 ? query.getEndTs() : entityView.getEndTimeMs();
  138 +
  139 + return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query :
  140 + new BaseReadTsKvQuery(query.getKey(), startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
  141 + }).collect(Collectors.toList());
  142 + }
  143 +
100 144 @Override
101 145 public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
102 146 validate(entityId);
... ...
... ... @@ -49,6 +49,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
49 49 import org.thingsboard.server.dao.model.ModelConstants;
50 50 import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao;
51 51 import org.thingsboard.server.dao.util.NoSqlDao;
  52 +import org.thingsboard.server.dao.util.NoSqlTsDao;
52 53
53 54 import javax.annotation.Nullable;
54 55 import javax.annotation.PostConstruct;
... ... @@ -70,7 +71,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
70 71 */
71 72 @Component
72 73 @Slf4j
73   -@NoSqlDao
  74 +@NoSqlTsDao
74 75 public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implements TimeseriesDao {
75 76
76 77 private static final int MIN_AGGREGATION_STEP_MS = 1000;
... ...
... ... @@ -34,7 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger;
34 34
35 35 @Component
36 36 @Slf4j
37   -@NoSqlDao
  37 +@NoSqlAnyDao
38 38 public class BufferedRateLimiter implements AsyncRateLimiter {
39 39
40 40 private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.util;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +
  20 +@ConditionalOnExpression("'${database.ts.type}'=='cassandra' || '${database.entities.type}'=='cassandra'")
  21 +public @interface NoSqlAnyDao {
  22 +}
... ...
... ... @@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util;
17 17
18 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 19
20   -@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "cassandra")
  20 +@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "cassandra")
21 21 public @interface NoSqlDao {
22 22 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.util;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  19 +
  20 +@ConditionalOnProperty(prefix = "database.ts", value = "type", havingValue = "cassandra")
  21 +public @interface NoSqlTsDao {
  22 +}
... ...
... ... @@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util;
17 17
18 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 19
20   -@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "sql")
  20 +@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql")
21 21 public @interface SqlDao {
22 22 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.util;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  19 +
  20 +@ConditionalOnProperty(prefix = "database.ts", value = "type", havingValue = "sql")
  21 +public @interface SqlTsDao {
  22 +}
... ...
dao/src/main/resources/cassandra/schema-entities.cql renamed from dao/src/main/resources/cassandra/schema.cql
... ... @@ -165,35 +165,55 @@ CREATE TABLE IF NOT EXISTS thingsboard.device (
165 165 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS
166 166 SELECT *
167 167 from thingsboard.device
168   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
  168 + WHERE tenant_id IS NOT NULL
  169 + AND customer_id IS NOT NULL
  170 + AND type IS NOT NULL
  171 + AND name IS NOT NULL
  172 + AND id IS NOT NULL
169 173 PRIMARY KEY ( tenant_id, name, id, customer_id, type)
170 174 WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
171 175
172 176 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS
173 177 SELECT *
174 178 from thingsboard.device
175   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  179 + WHERE tenant_id IS NOT NULL
  180 + AND customer_id IS NOT NULL
  181 + AND type IS NOT NULL
  182 + AND search_text IS NOT NULL
  183 + AND id IS NOT NULL
176 184 PRIMARY KEY ( tenant_id, search_text, id, customer_id, type)
177 185 WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
178 186
179 187 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS
180 188 SELECT *
181 189 from thingsboard.device
182   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  190 + WHERE tenant_id IS NOT NULL
  191 + AND customer_id IS NOT NULL
  192 + AND type IS NOT NULL
  193 + AND search_text IS NOT NULL
  194 + AND id IS NOT NULL
183 195 PRIMARY KEY ( tenant_id, type, search_text, id, customer_id)
184 196 WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC);
185 197
186 198 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS
187 199 SELECT *
188 200 from thingsboard.device
189   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  201 + WHERE tenant_id IS NOT NULL
  202 + AND customer_id IS NOT NULL
  203 + AND type IS NOT NULL
  204 + AND search_text IS NOT NULL
  205 + AND id IS NOT NULL
190 206 PRIMARY KEY ( customer_id, tenant_id, search_text, id, type )
191 207 WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
192 208
193 209 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS
194 210 SELECT *
195 211 from thingsboard.device
196   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  212 + WHERE tenant_id IS NOT NULL
  213 + AND customer_id IS NOT NULL
  214 + AND type IS NOT NULL
  215 + AND search_text IS NOT NULL
  216 + AND id IS NOT NULL
197 217 PRIMARY KEY ( customer_id, tenant_id, type, search_text, id )
198 218 WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC );
199 219
... ... @@ -378,41 +398,6 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_searc
378 398 PRIMARY KEY ( tenant_id, search_text, id )
379 399 WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
380 400
381   -CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
382   - entity_type text, // (DEVICE, CUSTOMER, TENANT)
383   - entity_id timeuuid,
384   - key text,
385   - partition bigint,
386   - ts bigint,
387   - bool_v boolean,
388   - str_v text,
389   - long_v bigint,
390   - dbl_v double,
391   - PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)
392   -);
393   -
394   -CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf (
395   - entity_type text, // (DEVICE, CUSTOMER, TENANT)
396   - entity_id timeuuid,
397   - key text,
398   - partition bigint,
399   - PRIMARY KEY (( entity_type, entity_id, key ), partition)
400   -) WITH CLUSTERING ORDER BY ( partition ASC )
401   - AND compaction = { 'class' : 'LeveledCompactionStrategy' };
402   -
403   -CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (
404   - entity_type text, // (DEVICE, CUSTOMER, TENANT)
405   - entity_id timeuuid,
406   - key text,
407   - ts bigint,
408   - bool_v boolean,
409   - str_v text,
410   - long_v bigint,
411   - dbl_v double,
412   - PRIMARY KEY (( entity_type, entity_id ), key)
413   -) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };
414   -
415   -
416 401 CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf (
417 402 entity_type text, // (DEVICE, CUSTOMER, TENANT)
418 403 entity_id timeuuid,
... ... @@ -638,3 +623,62 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
638 623 additional_info text,
639 624 PRIMARY KEY (id)
640 625 );
  626 +
  627 +CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
  628 + id timeuuid,
  629 + entity_id timeuuid,
  630 + entity_type text,
  631 + tenant_id timeuuid,
  632 + customer_id timeuuid,
  633 + name text,
  634 + keys text,
  635 + start_ts bigint,
  636 + end_ts bigint,
  637 + search_text text,
  638 + additional_info text,
  639 + PRIMARY KEY (id, entity_id, tenant_id, customer_id)
  640 +);
  641 +
  642 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
  643 + SELECT *
  644 + from thingsboard.entity_views
  645 + WHERE tenant_id IS NOT NULL
  646 + AND entity_id IS NOT NULL
  647 + AND customer_id IS NOT NULL
  648 + AND name IS NOT NULL
  649 + AND id IS NOT NULL
  650 + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
  651 + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
  652 +
  653 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
  654 + SELECT *
  655 + from thingsboard.entity_views
  656 + WHERE tenant_id IS NOT NULL
  657 + AND entity_id IS NOT NULL
  658 + AND customer_id IS NOT NULL
  659 + AND search_text IS NOT NULL
  660 + AND id IS NOT NULL
  661 + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
  662 + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
  663 +
  664 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
  665 + SELECT *
  666 + from thingsboard.entity_views
  667 + WHERE tenant_id IS NOT NULL
  668 + AND customer_id IS NOT NULL
  669 + AND entity_id IS NOT NULL
  670 + AND search_text IS NOT NULL
  671 + AND id IS NOT NULL
  672 + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
  673 + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
  674 +
  675 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
  676 + SELECT *
  677 + from thingsboard.entity_views
  678 + WHERE tenant_id IS NOT NULL
  679 + AND customer_id IS NOT NULL
  680 + AND entity_id IS NOT NULL
  681 + AND search_text IS NOT NULL
  682 + AND id IS NOT NULL
  683 + PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
  684 + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
... ...
  1 +--
  2 +-- Copyright © 2016-2018 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 +
  17 +CREATE KEYSPACE IF NOT EXISTS thingsboard
  18 +WITH replication = {
  19 + 'class' : 'SimpleStrategy',
  20 + 'replication_factor' : 1
  21 +};
  22 +
  23 +CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
  24 + entity_type text, // (DEVICE, CUSTOMER, TENANT)
  25 + entity_id timeuuid,
  26 + key text,
  27 + partition bigint,
  28 + ts bigint,
  29 + bool_v boolean,
  30 + str_v text,
  31 + long_v bigint,
  32 + dbl_v double,
  33 + PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)
  34 +);
  35 +
  36 +CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf (
  37 + entity_type text, // (DEVICE, CUSTOMER, TENANT)
  38 + entity_id timeuuid,
  39 + key text,
  40 + partition bigint,
  41 + PRIMARY KEY (( entity_type, entity_id, key ), partition)
  42 +) WITH CLUSTERING ORDER BY ( partition ASC )
  43 + AND compaction = { 'class' : 'LeveledCompactionStrategy' };
  44 +
  45 +CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (
  46 + entity_type text, // (DEVICE, CUSTOMER, TENANT)
  47 + entity_id timeuuid,
  48 + key text,
  49 + ts bigint,
  50 + bool_v boolean,
  51 + str_v text,
  52 + long_v bigint,
  53 + dbl_v double,
  54 + PRIMARY KEY (( entity_type, entity_id ), key)
  55 +) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };
... ...
dao/src/main/resources/sql/schema-entities.sql renamed from dao/src/main/resources/sql/schema.sql
... ... @@ -179,30 +179,6 @@ CREATE TABLE IF NOT EXISTS tenant (
179 179 zip varchar(255)
180 180 );
181 181
182   -CREATE TABLE IF NOT EXISTS ts_kv (
183   - entity_type varchar(255) NOT NULL,
184   - entity_id varchar(31) NOT NULL,
185   - key varchar(255) NOT NULL,
186   - ts bigint NOT NULL,
187   - bool_v boolean,
188   - str_v varchar(10000000),
189   - long_v bigint,
190   - dbl_v double precision,
191   - CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts)
192   -);
193   -
194   -CREATE TABLE IF NOT EXISTS ts_kv_latest (
195   - entity_type varchar(255) NOT NULL,
196   - entity_id varchar(31) NOT NULL,
197   - key varchar(255) NOT NULL,
198   - ts bigint NOT NULL,
199   - bool_v boolean,
200   - str_v varchar(10000000),
201   - long_v bigint,
202   - dbl_v double precision,
203   - CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key)
204   -);
205   -
206 182 CREATE TABLE IF NOT EXISTS user_credentials (
207 183 id varchar(31) NOT NULL CONSTRAINT user_credentials_pkey PRIMARY KEY,
208 184 activate_token varchar(255) UNIQUE,
... ... @@ -251,3 +227,17 @@ CREATE TABLE IF NOT EXISTS rule_node (
251 227 debug_mode boolean,
252 228 search_text varchar(255)
253 229 );
  230 +
  231 +CREATE TABLE IF NOT EXISTS entity_views (
  232 + id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
  233 + entity_id varchar(31),
  234 + entity_type varchar(255),
  235 + tenant_id varchar(31),
  236 + customer_id varchar(31),
  237 + name varchar(255),
  238 + keys varchar(255),
  239 + start_ts bigint,
  240 + end_ts bigint,
  241 + search_text varchar(255),
  242 + additional_info varchar
  243 +);
... ...
  1 +--
  2 +-- Copyright © 2016-2018 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 +
  17 +CREATE TABLE IF NOT EXISTS ts_kv (
  18 + entity_type varchar(255) NOT NULL,
  19 + entity_id varchar(31) NOT NULL,
  20 + key varchar(255) NOT NULL,
  21 + ts bigint NOT NULL,
  22 + bool_v boolean,
  23 + str_v varchar(10000000),
  24 + long_v bigint,
  25 + dbl_v double precision,
  26 + CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts)
  27 +);
  28 +
  29 +CREATE TABLE IF NOT EXISTS ts_kv_latest (
  30 + entity_type varchar(255) NOT NULL,
  31 + entity_id varchar(31) NOT NULL,
  32 + key varchar(255) NOT NULL,
  33 + ts bigint NOT NULL,
  34 + bool_v boolean,
  35 + str_v varchar(10000000),
  36 + long_v bigint,
  37 + dbl_v double precision,
  38 + CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key)
  39 +);
... ...
... ... @@ -30,7 +30,7 @@ public class JpaDaoTestSuite {
30 30
31 31 @ClassRule
32 32 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
33   - Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
  33 + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
34 34 "sql/drop-all-tables.sql",
35 35 "sql-test.properties"
36 36 );
... ...
... ... @@ -34,7 +34,9 @@ public class NoSqlDaoServiceTestSuite {
34 34 @ClassRule
35 35 public static CustomCassandraCQLUnit cassandraUnit =
36 36 new CustomCassandraCQLUnit(
37   - Arrays.asList(new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
  37 + Arrays.asList(
  38 + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
  39 + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
38 40 new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
39 41 new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)),
40 42 "cassandra-test.yaml", 30000L);
... ...
... ... @@ -30,7 +30,7 @@ public class SqlDaoServiceTestSuite {
30 30
31 31 @ClassRule
32 32 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
33   - Arrays.asList("sql/schema.sql", "sql/system-data.sql", "sql/system-test.sql"),
  33 + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql", "sql/system-test.sql"),
34 34 "sql/drop-all-tables.sql",
35 35 "sql-test.properties"
36 36 );
... ...
... ... @@ -24,6 +24,9 @@ caffeine.specs.devices.maxSize=100000
24 24 caffeine.specs.assets.timeToLiveInMinutes=1440
25 25 caffeine.specs.assets.maxSize=100000
26 26
  27 +caffeine.specs.entityViews.timeToLiveInMinutes=1440
  28 +caffeine.specs.entityViews.maxSize=100000
  29 +
27 30 caching.specs.devices.timeToLiveInMinutes=1440
28 31 caching.specs.devices.maxSize=100000
29 32
... ...
1   -database.type=cassandra
  1 +database.entities.type=cassandra
  2 +database.ts.type=cassandra
2 3
3 4 cassandra.queue.partitioning=HOURS
4 5 cassandra.queue.ack.ttl=3600
... ...
1   -database.type=sql
  1 +database.ts.type=sql
  2 +database.entities.type=sql
2 3
3 4 sql.ts_inserts_executor_type=fixed
4 5 sql.ts_inserts_fixed_thread_pool_size=10
... ...
... ... @@ -18,4 +18,5 @@ DROP TABLE IF EXISTS user_credentials;
18 18 DROP TABLE IF EXISTS widget_type;
19 19 DROP TABLE IF EXISTS widgets_bundle;
20 20 DROP TABLE IF EXISTS rule_node;
21   -DROP TABLE IF EXISTS rule_chain;
\ No newline at end of file
  21 +DROP TABLE IF EXISTS rule_chain;
  22 +DROP TABLE IF EXISTS entity_views;
... ...
... ... @@ -30,7 +30,9 @@ spec:
30 30 value: "cassandra-headless"
31 31 - name : CASSANDRA_PORT
32 32 value: "9042"
33   - - name : DATABASE_TYPE
  33 + - name : DATABASE_ENTITIES_TYPE
  34 + value: "cassandra"
  35 + - name : DATABASE_TS_TYPE
34 36 value: "cassandra"
35 37 - name : CASSANDRA_URL
36 38 value: "cassandra-headless:9042"
... ...
... ... @@ -30,7 +30,9 @@ spec:
30 30 value: "cassandra-headless"
31 31 - name : CASSANDRA_PORT
32 32 value: "9042"
33   - - name : DATABASE_TYPE
  33 + - name : DATABASE_ENTITIES_TYPE
  34 + value: "cassandra"
  35 + - name : DATABASE_TS_TYPE
34 36 value: "cassandra"
35 37 - name : CASSANDRA_URL
36 38 value: "cassandra-headless:9042"
... ...
... ... @@ -120,7 +120,12 @@ spec:
120 120 configMapKeyRef:
121 121 name: tb-config
122 122 key: cassandra.url
123   - - name: DATABASE_TYPE
  123 + - name: DATABASE_ENTITIES_TYPE
  124 + valueFrom:
  125 + configMapKeyRef:
  126 + name: tb-config
  127 + key: database.type
  128 + - name: DATABASE_TS_TYPE
124 129 valueFrom:
125 130 configMapKeyRef:
126 131 name: tb-config
... ...
... ... @@ -8,7 +8,8 @@ COAP_BIND_PORT=5683
8 8 ZOOKEEPER_URL=zk:2181
9 9
10 10 # type of database to use: sql[DEFAULT] or cassandra
11   -DATABASE_TYPE=sql
  11 +DATABASE_TS_TYPE=sql
  12 +DATABASE_ENTITIES_TYPE=sql
12 13
13 14 # cassandra db config
14 15 CASSANDRA_URL=cassandra:9042
... ...
... ... @@ -23,7 +23,7 @@ printenv | awk -F "=" '{print "export " $1 "='\''" $2 "'\''"}' >> /usr/share/thi
23 23
24 24 cat /usr/share/thingsboard/conf/thingsboard.conf
25 25
26   -if [ "$DATABASE_TYPE" == "cassandra" ]; then
  26 +if [ "$DATABASE_ENTITIES_TYPE" == "cassandra" ]; then
27 27 until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open\|filtered"
28 28 do
29 29 echo "Wait for cassandra db to start..."
... ... @@ -31,7 +31,7 @@ if [ "$DATABASE_TYPE" == "cassandra" ]; then
31 31 done
32 32 fi
33 33
34   -if [ "$DATABASE_TYPE" == "sql" ]; then
  34 +if [ "$DATABASE_ENTITIES_TYPE" == "sql" ]; then
35 35 if [ "$SPRING_DRIVER_CLASS_NAME" == "org.postgresql.Driver" ]; then
36 36 until nmap $POSTGRES_HOST -p $POSTGRES_PORT | grep "$POSTGRES_PORT/tcp open"
37 37 do
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.asset.AssetService;
26 26 import org.thingsboard.server.dao.attributes.AttributesService;
27 27 import org.thingsboard.server.dao.customer.CustomerService;
28 28 import org.thingsboard.server.dao.device.DeviceService;
  29 +import org.thingsboard.server.dao.entityview.EntityViewService;
29 30 import org.thingsboard.server.dao.relation.RelationService;
30 31 import org.thingsboard.server.dao.rule.RuleChainService;
31 32 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -83,6 +84,8 @@ public interface TbContext {
83 84
84 85 RelationService getRelationService();
85 86
  87 + EntityViewService getEntityViewService();
  88 +
86 89 ListeningExecutor getJsExecutor();
87 90
88 91 ListeningExecutor getMailExecutor();
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.rule.engine.action;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.gson.JsonParser;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
  23 +import org.thingsboard.rule.engine.api.RuleNode;
  24 +import org.thingsboard.rule.engine.api.TbContext;
  25 +import org.thingsboard.rule.engine.api.TbNode;
  26 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  27 +import org.thingsboard.rule.engine.api.TbNodeException;
  28 +import org.thingsboard.rule.engine.api.TbRelationTypes;
  29 +import org.thingsboard.rule.engine.api.util.DonAsynchron;
  30 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  31 +import org.thingsboard.server.common.data.DataConstants;
  32 +import org.thingsboard.server.common.data.EntityView;
  33 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  34 +import org.thingsboard.server.common.data.plugin.ComponentType;
  35 +import org.thingsboard.server.common.msg.TbMsg;
  36 +import org.thingsboard.server.common.msg.session.SessionMsgType;
  37 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  38 +
  39 +import javax.annotation.Nullable;
  40 +import java.util.List;
  41 +import java.util.Set;
  42 +import java.util.concurrent.ExecutionException;
  43 +import java.util.stream.Collectors;
  44 +
  45 +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
  46 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
  47 +
  48 +@Slf4j
  49 +@RuleNode(
  50 + type = ComponentType.ACTION,
  51 + name = "copy attributes",
  52 + configClazz = EmptyNodeConfiguration.class,
  53 + nodeDescription = "Copy attributes from asset/device to entity view and changes message originator to related entity view",
  54 + nodeDetails = "Copy attributes from asset/device to related entity view according to entity view configuration. \n " +
  55 + "Copy will be done only for attributes that are between start and end dates and according to attribute keys configuration. \n" +
  56 + "Changes message originator to related entity view and produces new messages according to count of updated entity views",
  57 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  58 + configDirective = "tbNodeEmptyConfig",
  59 + icon = "content_copy"
  60 +)
  61 +public class TbCopyAttributesToEntityViewNode implements TbNode {
  62 +
  63 + EmptyNodeConfiguration config;
  64 +
  65 + @Override
  66 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  67 + this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class);
  68 + }
  69 +
  70 + @Override
  71 + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
  72 + if (!msg.getMetaData().getData().isEmpty()) {
  73 + long now = System.currentTimeMillis();
  74 + String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ?
  75 + DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope");
  76 +
  77 + ListenableFuture<List<EntityView>> entityViewsFuture =
  78 + ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator());
  79 +
  80 + DonAsynchron.withCallback(entityViewsFuture,
  81 + entityViews -> {
  82 + for (EntityView entityView : entityViews) {
  83 + long startTime = entityView.getStartTimeMs();
  84 + long endTime = entityView.getEndTimeMs();
  85 + if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) {
  86 + Set<AttributeKvEntry> attributes =
  87 + JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes();
  88 + List<AttributeKvEntry> filteredAttributes =
  89 + attributes.stream()
  90 + .filter(attr -> {
  91 + switch (scope) {
  92 + case DataConstants.CLIENT_SCOPE:
  93 + if (entityView.getKeys().getAttributes().getCs().isEmpty()) {
  94 + return true;
  95 + }
  96 + return entityView.getKeys().getAttributes().getCs().contains(attr.getKey());
  97 + case DataConstants.SERVER_SCOPE:
  98 + if (entityView.getKeys().getAttributes().getSs().isEmpty()) {
  99 + return true;
  100 + }
  101 + return entityView.getKeys().getAttributes().getSs().contains(attr.getKey());
  102 + case DataConstants.SHARED_SCOPE:
  103 + if (entityView.getKeys().getAttributes().getSh().isEmpty()) {
  104 + return true;
  105 + }
  106 + return entityView.getKeys().getAttributes().getSh().contains(attr.getKey());
  107 + }
  108 + return false;
  109 + }).collect(Collectors.toList());
  110 +
  111 + ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes,
  112 + new FutureCallback<Void>() {
  113 + @Override
  114 + public void onSuccess(@Nullable Void result) {
  115 + TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData());
  116 + ctx.tellNext(updMsg, SUCCESS);
  117 + }
  118 +
  119 + @Override
  120 + public void onFailure(Throwable t) {
  121 + ctx.tellFailure(msg, t);
  122 + }
  123 + });
  124 + }
  125 + }
  126 + },
  127 + t -> ctx.tellFailure(msg, t));
  128 + } else {
  129 + ctx.tellNext(msg, FAILURE);
  130 + }
  131 + }
  132 +
  133 + @Override
  134 + public void destroy() {
  135 +
  136 + }
  137 +}
... ...
  1 +/*
  2 + * Copyright © 2016-2018 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 +import thingsboardTypes from '../common/types.constant';
  17 +
  18 +export default angular.module('thingsboard.api.entityView', [thingsboardTypes])
  19 + .factory('entityViewService', EntityViewService)
  20 + .name;
  21 +
  22 +/*@ngInject*/
  23 +function EntityViewService($http, $q, $window, userService, attributeService, customerService, types) {
  24 +
  25 + var service = {
  26 + assignEntityViewToCustomer: assignEntityViewToCustomer,
  27 + deleteEntityView: deleteEntityView,
  28 + getCustomerEntityViews: getCustomerEntityViews,
  29 + getEntityView: getEntityView,
  30 + getEntityViews: getEntityViews,
  31 + getTenantEntityViews: getTenantEntityViews,
  32 + saveEntityView: saveEntityView,
  33 + unassignEntityViewFromCustomer: unassignEntityViewFromCustomer,
  34 + getEntityViewAttributes: getEntityViewAttributes,
  35 + subscribeForEntityViewAttributes: subscribeForEntityViewAttributes,
  36 + unsubscribeForEntityViewAttributes: unsubscribeForEntityViewAttributes,
  37 + findByQuery: findByQuery,
  38 + getEntityViewTypes: getEntityViewTypes
  39 + }
  40 +
  41 + return service;
  42 +
  43 + function getTenantEntityViews(pageLink, applyCustomersInfo, config, type) {
  44 + var deferred = $q.defer();
  45 + var url = '/api/tenant/entityViews?limit=' + pageLink.limit;
  46 + if (angular.isDefined(pageLink.textSearch)) {
  47 + url += '&textSearch=' + pageLink.textSearch;
  48 + }
  49 + if (angular.isDefined(pageLink.idOffset)) {
  50 + url += '&idOffset=' + pageLink.idOffset;
  51 + }
  52 + if (angular.isDefined(pageLink.textOffset)) {
  53 + url += '&textOffset=' + pageLink.textOffset;
  54 + }
  55 + if (angular.isDefined(type) && type.length) {
  56 + url += '&type=' + type;
  57 + }
  58 + $http.get(url, config).then(function success(response) {
  59 + if (applyCustomersInfo) {
  60 + customerService.applyAssignedCustomersInfo(response.data.data).then(
  61 + function success(data) {
  62 + response.data.data = data;
  63 + deferred.resolve(response.data);
  64 + },
  65 + function fail() {
  66 + deferred.reject();
  67 + }
  68 + );
  69 + } else {
  70 + deferred.resolve(response.data);
  71 + }
  72 + }, function fail() {
  73 + deferred.reject();
  74 + });
  75 + return deferred.promise;
  76 + }
  77 +
  78 + function getCustomerEntityViews(customerId, pageLink, applyCustomersInfo, config, type) {
  79 + var deferred = $q.defer();
  80 + var url = '/api/customer/' + customerId + '/entityViews?limit=' + pageLink.limit;
  81 + if (angular.isDefined(pageLink.textSearch)) {
  82 + url += '&textSearch=' + pageLink.textSearch;
  83 + }
  84 + if (angular.isDefined(pageLink.idOffset)) {
  85 + url += '&idOffset=' + pageLink.idOffset;
  86 + }
  87 + if (angular.isDefined(pageLink.textOffset)) {
  88 + url += '&textOffset=' + pageLink.textOffset;
  89 + }
  90 + if (angular.isDefined(type) && type.length) {
  91 + url += '&type=' + type;
  92 + }
  93 + $http.get(url, config).then(function success(response) {
  94 + if (applyCustomersInfo) {
  95 + customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
  96 + function success(data) {
  97 + response.data.data = data;
  98 + deferred.resolve(response.data);
  99 + },
  100 + function fail() {
  101 + deferred.reject();
  102 + }
  103 + );
  104 + } else {
  105 + deferred.resolve(response.data);
  106 + }
  107 + }, function fail() {
  108 + deferred.reject();
  109 + });
  110 +
  111 + return deferred.promise;
  112 + }
  113 +
  114 + function getEntityView(entityViewId, ignoreErrors, config) {
  115 + var deferred = $q.defer();
  116 + var url = '/api/entityView/' + entityViewId;
  117 + if (!config) {
  118 + config = {};
  119 + }
  120 + config = Object.assign(config, { ignoreErrors: ignoreErrors });
  121 + $http.get(url, config).then(function success(response) {
  122 + deferred.resolve(response.data);
  123 + }, function fail(response) {
  124 + deferred.reject(response.data);
  125 + });
  126 + return deferred.promise;
  127 + }
  128 +
  129 + function getEntityViews(entityViewIds, config) {
  130 + var deferred = $q.defer();
  131 + var ids = '';
  132 + for (var i=0;i<entityViewIds.length;i++) {
  133 + if (i>0) {
  134 + ids += ',';
  135 + }
  136 + ids += entityViewIds[i];
  137 + }
  138 + var url = '/api/entityViews?entityViewIds=' + ids;
  139 + $http.get(url, config).then(function success(response) {
  140 + var entityViews = response.data;
  141 + entityViews.sort(function (entityView1, entityView2) {
  142 + var id1 = entityView1.id.id;
  143 + var id2 = entityView2.id.id;
  144 + var index1 = entityViewIds.indexOf(id1);
  145 + var index2 = entityViewIds.indexOf(id2);
  146 + return index1 - index2;
  147 + });
  148 + deferred.resolve(entityViews);
  149 + }, function fail(response) {
  150 + deferred.reject(response.data);
  151 + });
  152 + return deferred.promise;
  153 + }
  154 +
  155 + function saveEntityView(entityView) {
  156 + var deferred = $q.defer();
  157 + var url = '/api/entityView';
  158 +
  159 + $http.post(url, entityView).then(function success(response) {
  160 + deferred.resolve(response.data);
  161 + }, function fail() {
  162 + deferred.reject();
  163 + });
  164 + return deferred.promise;
  165 + }
  166 +
  167 + function deleteEntityView(entityViewId) {
  168 + var deferred = $q.defer();
  169 + var url = '/api/entityView/' + entityViewId;
  170 + $http.delete(url).then(function success() {
  171 + deferred.resolve();
  172 + }, function fail() {
  173 + deferred.reject();
  174 + });
  175 + return deferred.promise;
  176 + }
  177 +
  178 + function assignEntityViewToCustomer(customerId, entityViewId) {
  179 + var deferred = $q.defer();
  180 + var url = '/api/customer/' + customerId + '/entityView/' + entityViewId;
  181 + $http.post(url, null).then(function success(response) {
  182 + deferred.resolve(response.data);
  183 + }, function fail() {
  184 + deferred.reject();
  185 + });
  186 + return deferred.promise;
  187 + }
  188 +
  189 + function unassignEntityViewFromCustomer(entityViewId) {
  190 + var deferred = $q.defer();
  191 + var url = '/api/customer/entityView/' + entityViewId;
  192 + $http.delete(url).then(function success(response) {
  193 + deferred.resolve(response.data);
  194 + }, function fail() {
  195 + deferred.reject();
  196 + });
  197 + return deferred.promise;
  198 + }
  199 +
  200 + function getEntityViewAttributes(entityViewId, attributeScope, query, successCallback, config) {
  201 + return attributeService.getEntityAttributes(types.entityType.entityView, entityViewId, attributeScope, query, successCallback, config);
  202 + }
  203 +
  204 + function subscribeForEntityViewAttributes(entityViewId, attributeScope) {
  205 + return attributeService.subscribeForEntityAttributes(types.entityType.entityView, entityViewId, attributeScope);
  206 + }
  207 +
  208 + function unsubscribeForEntityViewAttributes(subscriptionId) {
  209 + attributeService.unsubscribeForEntityAttributes(subscriptionId);
  210 + }
  211 +
  212 + function findByQuery(query, ignoreErrors, config) {
  213 + var deferred = $q.defer();
  214 + var url = '/api/entityViews';
  215 + if (!config) {
  216 + config = {};
  217 + }
  218 + config = Object.assign(config, { ignoreErrors: ignoreErrors });
  219 + $http.post(url, query, config).then(function success(response) {
  220 + deferred.resolve(response.data);
  221 + }, function fail() {
  222 + deferred.reject();
  223 + });
  224 + return deferred.promise;
  225 + }
  226 +
  227 + function getEntityViewTypes(config) {
  228 + var deferred = $q.defer();
  229 + var url = '/api/entityView/types';
  230 + $http.get(url, config).then(function success(response) {
  231 + deferred.resolve(response.data);
  232 + }, function fail() {
  233 + deferred.reject();
  234 + });
  235 + return deferred.promise;
  236 + }
  237 +
  238 +}
... ...
... ... @@ -20,8 +20,9 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
20 20 .name;
21 21
22 22 /*@ngInject*/
23   -function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
24   - assetService, tenantService, customerService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) {
  23 +function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, assetService, tenantService,
  24 + customerService, ruleChainService, dashboardService, entityRelationService, attributeService,
  25 + entityViewService, types, utils) {
25 26 var service = {
26 27 getEntity: getEntity,
27 28 getEntities: getEntities,
... ... @@ -54,6 +55,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
54 55 case types.entityType.asset:
55 56 promise = assetService.getAsset(entityId, true, config);
56 57 break;
  58 + case types.entityType.entityView:
  59 + promise = entityViewService.getEntityView(entityId, true, config);
  60 + break;
57 61 case types.entityType.tenant:
58 62 promise = tenantService.getTenant(entityId, config);
59 63 break;
... ... @@ -239,6 +243,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
239 243 promise = assetService.getTenantAssets(pageLink, false, config, subType);
240 244 }
241 245 break;
  246 + case types.entityType.entityView:
  247 + if (user.authority === 'CUSTOMER_USER') {
  248 + promise = entityViewService.getCustomerEntityViews(customerId, pageLink, false, config, subType);
  249 + } else {
  250 + promise = entityViewService.getTenantEntityViews(pageLink, false, config, subType);
  251 + }
  252 + break;
242 253 case types.entityType.tenant:
243 254 if (user.authority === 'TENANT_ADMIN') {
244 255 promise = getSingleTenantByPageLinkPromise(pageLink, config);
... ... @@ -725,6 +736,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
725 736 case 'TENANT_ADMIN':
726 737 entityTypes.device = types.entityType.device;
727 738 entityTypes.asset = types.entityType.asset;
  739 + entityTypes.entityView = types.entityType.entityView;
728 740 entityTypes.tenant = types.entityType.tenant;
729 741 entityTypes.customer = types.entityType.customer;
730 742 entityTypes.dashboard = types.entityType.dashboard;
... ... @@ -735,6 +747,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
735 747 case 'CUSTOMER_USER':
736 748 entityTypes.device = types.entityType.device;
737 749 entityTypes.asset = types.entityType.asset;
  750 + entityTypes.entityView = types.entityType.entityView;
738 751 entityTypes.customer = types.entityType.customer;
739 752 entityTypes.dashboard = types.entityType.dashboard;
740 753 if (useAliasEntityTypes) {
... ...
... ... @@ -67,6 +67,7 @@ import thingsboardClipboard from './services/clipboard.service';
67 67 import thingsboardHome from './layout';
68 68 import thingsboardApiLogin from './api/login.service';
69 69 import thingsboardApiDevice from './api/device.service';
  70 +import thingsboardApiEntityView from './api/entity-view.service';
70 71 import thingsboardApiUser from './api/user.service';
71 72 import thingsboardApiEntityRelation from './api/entity-relation.service';
72 73 import thingsboardApiAsset from './api/asset.service';
... ... @@ -133,6 +134,7 @@ angular.module('thingsboard', [
133 134 thingsboardHome,
134 135 thingsboardApiLogin,
135 136 thingsboardApiDevice,
  137 + thingsboardApiEntityView,
136 138 thingsboardApiUser,
137 139 thingsboardApiEntityRelation,
138 140 thingsboardApiAsset,
... ...
... ... @@ -327,7 +327,8 @@ export default angular.module('thingsboard.types', [])
327 327 dashboard: "DASHBOARD",
328 328 alarm: "ALARM",
329 329 rulechain: "RULE_CHAIN",
330   - rulenode: "RULE_NODE"
  330 + rulenode: "RULE_NODE",
  331 + entityView: "ENTITY_VIEW"
331 332 },
332 333 aliasEntityType: {
333 334 current_customer: "CURRENT_CUSTOMER"
... ... @@ -345,6 +346,12 @@ export default angular.module('thingsboard.types', [])
345 346 list: 'entity.list-of-assets',
346 347 nameStartsWith: 'entity.asset-name-starts-with'
347 348 },
  349 + "ENTITY_VIEW": {
  350 + type: 'entity.type-entity-view',
  351 + typePlural: 'entity.type-entity-views',
  352 + list: 'entity.list-of-entity-views',
  353 + nameStartsWith: 'entity.entity-view-name-starts-with'
  354 + },
348 355 "TENANT": {
349 356 type: 'entity.type-tenant',
350 357 typePlural: 'entity.type-tenants',
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'entity-view.add' | translate }}" tb-help="'entityViews'" help-container-id="help-container">
  19 + <form name="theForm" ng-submit="vm.add()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>entity-view.add</h2>
  23 + <span flex></span>
  24 + <div id="help-container"></div>
  25 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  26 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  27 + </md-button>
  28 + </div>
  29 + </md-toolbar>
  30 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  31 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  32 + <md-dialog-content>
  33 + <div class="md-dialog-content">
  34 + <tb-entity-view entity-view="vm.item" is-edit="true" the-form="theForm"></tb-entity-view>
  35 + </div>
  36 + </md-dialog-content>
  37 + <md-dialog-actions layout="row">
  38 + <span flex></span>
  39 + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
  40 + {{ 'action.add' | translate }}
  41 + </md-button>
  42 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
  43 + </md-dialog-actions>
  44 + </form>
  45 +</md-dialog>
\ No newline at end of file
... ...
  1 +/*
  2 + * Copyright © 2016-2018 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 +/*@ngInject*/
  17 +export default function AddEntityViewsToCustomerController(entityViewService, $mdDialog, $q, customerId, entityViews) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.entityViews = entityViews;
  22 + vm.searchText = '';
  23 +
  24 + vm.assign = assign;
  25 + vm.cancel = cancel;
  26 + vm.hasData = hasData;
  27 + vm.noData = noData;
  28 + vm.searchEntityViewTextUpdated = searchEntityViewTextUpdated;
  29 + vm.toggleEntityViewSelection = toggleEntityViewSelection;
  30 +
  31 + vm.theEntityViews = {
  32 + getItemAtIndex: function (index) {
  33 + if (index > vm.entityViews.data.length) {
  34 + vm.theEntityViews.fetchMoreItems_(index);
  35 + return null;
  36 + }
  37 + var item = vm.entityViews.data[index];
  38 + if (item) {
  39 + item.indexNumber = index + 1;
  40 + }
  41 + return item;
  42 + },
  43 +
  44 + getLength: function () {
  45 + if (vm.entityViews.hasNext) {
  46 + return vm.entityViews.data.length + vm.entityViews.nextPageLink.limit;
  47 + } else {
  48 + return vm.entityViews.data.length;
  49 + }
  50 + },
  51 +
  52 + fetchMoreItems_: function () {
  53 + if (vm.entityViews.hasNext && !vm.entityViews.pending) {
  54 + vm.entityViews.pending = true;
  55 + entityViewService.getTenantEntityViews(vm.entityViews.nextPageLink, false).then(
  56 + function success(entityViews) {
  57 + vm.entityViews.data = vm.entityViews.data.concat(entityViews.data);
  58 + vm.entityViews.nextPageLink = entityViews.nextPageLink;
  59 + vm.entityViews.hasNext = entityViews.hasNext;
  60 + if (vm.entityViews.hasNext) {
  61 + vm.entityViews.nextPageLink.limit = vm.entityViews.pageSize;
  62 + }
  63 + vm.entityViews.pending = false;
  64 + },
  65 + function fail() {
  66 + vm.entityViews.hasNext = false;
  67 + vm.entityViews.pending = false;
  68 + });
  69 + }
  70 + }
  71 + };
  72 +
  73 + function cancel () {
  74 + $mdDialog.cancel();
  75 + }
  76 +
  77 + function assign() {
  78 + var tasks = [];
  79 + for (var entityViewId in vm.entityViews.selections) {
  80 + tasks.push(entityViewService.assignEntityViewToCustomer(customerId, entityViewId));
  81 + }
  82 + $q.all(tasks).then(function () {
  83 + $mdDialog.hide();
  84 + });
  85 + }
  86 +
  87 + function noData() {
  88 + return vm.entityViews.data.length == 0 && !vm.entityViews.hasNext;
  89 + }
  90 +
  91 + function hasData() {
  92 + return vm.entityViews.data.length > 0;
  93 + }
  94 +
  95 + function toggleEntityViewSelection($event, entityView) {
  96 + $event.stopPropagation();
  97 + var selected = angular.isDefined(entityView.selected) && entityView.selected;
  98 + entityView.selected = !selected;
  99 + if (entityView.selected) {
  100 + vm.entityViews.selections[entityView.id.id] = true;
  101 + vm.entityViews.selectedCount++;
  102 + } else {
  103 + delete vm.entityViews.selections[entityView.id.id];
  104 + vm.entityViews.selectedCount--;
  105 + }
  106 + }
  107 +
  108 + function searchEntityViewTextUpdated() {
  109 + vm.entityViews = {
  110 + pageSize: vm.entityViews.pageSize,
  111 + data: [],
  112 + nextPageLink: {
  113 + limit: vm.entityViews.pageSize,
  114 + textSearch: vm.searchText
  115 + },
  116 + selections: {},
  117 + selectedCount: 0,
  118 + hasNext: true,
  119 + pending: false
  120 + };
  121 + }
  122 +
  123 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'entity-view.assign-to-customer' | translate }}">
  19 + <form name="theForm" ng-submit="vm.assign()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>entity-view.assign-entity-view-to-customer</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <fieldset>
  34 + <span translate>entity-view.assign-entity-view-to-customer-text</span>
  35 + <md-input-container class="md-block" style='margin-bottom: 0px;'>
  36 + <label>&nbsp;</label>
  37 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
  38 + search
  39 + </md-icon>
  40 + <input id="entity-view-search" autofocus ng-model="vm.searchText"
  41 + ng-change="vm.searchEntityViewTextUpdated()"
  42 + placeholder="{{ 'common.enter-search' | translate }}"/>
  43 + </md-input-container>
  44 + <div style='min-height: 150px;'>
  45 + <span translate layout-align="center center"
  46 + style="text-transform: uppercase; display: flex; height: 150px;"
  47 + class="md-subhead"
  48 + ng-show="vm.noData()">entity-view.no-entity-views-text</span>
  49 + <md-virtual-repeat-container ng-show="vm.hasData()"
  50 + tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
  51 + style='min-height: 150px; width: 100%;'>
  52 + <md-list>
  53 + <md-list-item md-virtual-repeat="entityView in vm.theEntityViews" md-on-demand
  54 + class="repeated-item" flex>
  55 + <md-checkbox ng-click="vm.toggleEntityViewSelection($event, entityView)"
  56 + aria-label="{{ 'item.selected' | translate }}"
  57 + ng-checked="entityView.selected"></md-checkbox>
  58 + <span> {{ entityView.name }} </span>
  59 + </md-list-item>
  60 + </md-list>
  61 + </md-virtual-repeat-container>
  62 + </div>
  63 + </fieldset>
  64 + </div>
  65 + </md-dialog-content>
  66 + <md-dialog-actions layout="row">
  67 + <span flex></span>
  68 + <md-button ng-disabled="$root.loading || vm.entityViews.selectedCount == 0" type="submit"
  69 + class="md-raised md-primary">
  70 + {{ 'action.assign' | translate }}
  71 + </md-button>
  72 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
  73 + translate }}
  74 + </md-button>
  75 + </md-dialog-actions>
  76 + </form>
  77 +</md-dialog>
\ No newline at end of file
... ...
  1 +/*
  2 + * Copyright © 2016-2018 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 +/*@ngInject*/
  17 +export default function AssignEntityViewToCustomerController(customerService, entityViewService, $mdDialog, $q, entityViewIds, customers) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.customers = customers;
  22 + vm.searchText = '';
  23 +
  24 + vm.assign = assign;
  25 + vm.cancel = cancel;
  26 + vm.isCustomerSelected = isCustomerSelected;
  27 + vm.hasData = hasData;
  28 + vm.noData = noData;
  29 + vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
  30 + vm.toggleCustomerSelection = toggleCustomerSelection;
  31 +
  32 + vm.theCustomers = {
  33 + getItemAtIndex: function (index) {
  34 + if (index > vm.customers.data.length) {
  35 + vm.theCustomers.fetchMoreItems_(index);
  36 + return null;
  37 + }
  38 + var item = vm.customers.data[index];
  39 + if (item) {
  40 + item.indexNumber = index + 1;
  41 + }
  42 + return item;
  43 + },
  44 +
  45 + getLength: function () {
  46 + if (vm.customers.hasNext) {
  47 + return vm.customers.data.length + vm.customers.nextPageLink.limit;
  48 + } else {
  49 + return vm.customers.data.length;
  50 + }
  51 + },
  52 +
  53 + fetchMoreItems_: function () {
  54 + if (vm.customers.hasNext && !vm.customers.pending) {
  55 + vm.customers.pending = true;
  56 + customerService.getCustomers(vm.customers.nextPageLink).then(
  57 + function success(customers) {
  58 + vm.customers.data = vm.customers.data.concat(customers.data);
  59 + vm.customers.nextPageLink = customers.nextPageLink;
  60 + vm.customers.hasNext = customers.hasNext;
  61 + if (vm.customers.hasNext) {
  62 + vm.customers.nextPageLink.limit = vm.customers.pageSize;
  63 + }
  64 + vm.customers.pending = false;
  65 + },
  66 + function fail() {
  67 + vm.customers.hasNext = false;
  68 + vm.customers.pending = false;
  69 + });
  70 + }
  71 + }
  72 + };
  73 +
  74 + function cancel() {
  75 + $mdDialog.cancel();
  76 + }
  77 +
  78 + function assign() {
  79 + var tasks = [];
  80 + for (var i=0; i < entityViewIds.length;i++) {
  81 + tasks.push(entityViewService.assignEntityViewToCustomer(vm.customers.selection.id.id, entityViewIds[i]));
  82 + }
  83 + $q.all(tasks).then(function () {
  84 + $mdDialog.hide();
  85 + });
  86 + }
  87 +
  88 + function noData() {
  89 + return vm.customers.data.length == 0 && !vm.customers.hasNext;
  90 + }
  91 +
  92 + function hasData() {
  93 + return vm.customers.data.length > 0;
  94 + }
  95 +
  96 + function toggleCustomerSelection($event, customer) {
  97 + $event.stopPropagation();
  98 + if (vm.isCustomerSelected(customer)) {
  99 + vm.customers.selection = null;
  100 + } else {
  101 + vm.customers.selection = customer;
  102 + }
  103 + }
  104 +
  105 + function isCustomerSelected(customer) {
  106 + return vm.customers.selection != null && customer &&
  107 + customer.id.id === vm.customers.selection.id.id;
  108 + }
  109 +
  110 + function searchCustomerTextUpdated() {
  111 + vm.customers = {
  112 + pageSize: vm.customers.pageSize,
  113 + data: [],
  114 + nextPageLink: {
  115 + limit: vm.customers.pageSize,
  116 + textSearch: vm.searchText
  117 + },
  118 + selection: null,
  119 + hasNext: true,
  120 + pending: false
  121 + };
  122 + }
  123 +}
... ...