Browse Source

Merge branch 'master' into develop/2.2

pull/1150/head
Igor Kulikov 8 years ago
parent
commit
7d7d5cbdbc
  1. 81
      application/src/main/data/upgrade/2.1.1/schema_update.cql
  2. 31
      application/src/main/data/upgrade/2.1.1/schema_update.sql
  3. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 26
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  6. 218
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  7. 21
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  8. 19
      application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java
  9. 30
      application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java
  10. 30
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java
  11. 19
      application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
  12. 19
      application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java
  13. 9
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  14. 30
      application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
  15. 30
      application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java
  16. 19
      application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java
  17. 31
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  18. 69
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  19. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/sub/Subscription.java
  20. 13
      application/src/main/resources/thingsboard.yml
  21. 456
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  22. 3
      application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java
  23. 4
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  24. 26
      application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java
  25. 31
      application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
  26. 3
      application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java
  27. 4
      application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java
  28. 3
      application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java
  29. 2
      application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
  30. 3
      application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java
  31. 2
      application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
  32. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  34. 78
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  35. 42
      common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
  36. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  37. 44
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java
  38. 4
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java
  39. 47
      common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java
  40. 43
      common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java
  41. 17
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  42. 4
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java
  43. 4
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java
  44. 4
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraQueryOptions.java
  45. 4
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraSocketOptions.java
  46. 5
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  47. 22
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  48. 121
      dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
  49. 73
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
  50. 58
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  51. 356
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  52. 20
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  53. 157
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
  54. 158
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
  55. 56
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
  56. 103
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
  57. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
  58. 5
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  59. 44
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  60. 3
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  61. 2
      dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java
  62. 22
      dao/src/main/java/org/thingsboard/server/dao/util/NoSqlAnyDao.java
  63. 2
      dao/src/main/java/org/thingsboard/server/dao/util/NoSqlDao.java
  64. 22
      dao/src/main/java/org/thingsboard/server/dao/util/NoSqlTsDao.java
  65. 2
      dao/src/main/java/org/thingsboard/server/dao/util/SqlDao.java
  66. 22
      dao/src/main/java/org/thingsboard/server/dao/util/SqlTsDao.java
  67. 124
      dao/src/main/resources/cassandra/schema-entities.cql
  68. 55
      dao/src/main/resources/cassandra/schema-ts.cql
  69. 38
      dao/src/main/resources/sql/schema-entities.sql
  70. 39
      dao/src/main/resources/sql/schema-ts.sql
  71. 2
      dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java
  72. 4
      dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java
  73. 2
      dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java
  74. 3
      dao/src/test/resources/application-test.properties
  75. 3
      dao/src/test/resources/nosql-test.properties
  76. 3
      dao/src/test/resources/sql-test.properties
  77. 3
      dao/src/test/resources/sql/drop-all-tables.sql
  78. 4
      docker/k8s/cassandra-setup.yaml
  79. 4
      docker/k8s/cassandra-upgrade.yaml
  80. 7
      docker/k8s/tb.yaml
  81. 3
      docker/tb.env
  82. 4
      docker/tb/run-application.sh
  83. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  84. 137
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
  85. 238
      ui/src/app/api/entity-view.service.js
  86. 17
      ui/src/app/api/entity.service.js
  87. 2
      ui/src/app/app.js
  88. 9
      ui/src/app/common/types.constant.js
  89. 45
      ui/src/app/entity-view/add-entity-view.tpl.html
  90. 123
      ui/src/app/entity-view/add-entity-views-to-customer.controller.js
  91. 77
      ui/src/app/entity-view/add-entity-views-to-customer.tpl.html
  92. 123
      ui/src/app/entity-view/assign-to-customer.controller.js
  93. 76
      ui/src/app/entity-view/assign-to-customer.tpl.html
  94. 22
      ui/src/app/entity-view/entity-view-card.tpl.html
  95. 119
      ui/src/app/entity-view/entity-view-fieldset.tpl.html
  96. 483
      ui/src/app/entity-view/entity-view.controller.js
  97. 121
      ui/src/app/entity-view/entity-view.directive.js
  98. 72
      ui/src/app/entity-view/entity-view.routes.js
  99. 75
      ui/src/app/entity-view/entity-views.tpl.html
  100. 41
      ui/src/app/entity-view/index.js

81
application/src/main/data/upgrade/2.1.1/schema_update.cql

@ -0,0 +1,81 @@
--
-- Copyright © 2016-2018 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
DROP TABLE IF EXISTS thingsboard.entity_views;
ControllerSqlTestSuite
CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
id timeuuid,
entity_id timeuuid,
entity_type text,
tenant_id timeuuid,
customer_id timeuuid,
name text,
keys text,
start_ts bigint,
end_ts bigint,
search_text text,
additional_info text,
PRIMARY KEY (id, entity_id, tenant_id, customer_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND name IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);

31
application/src/main/data/upgrade/2.1.1/schema_update.sql

@ -0,0 +1,31 @@
--
-- Copyright © 2016-2018 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP TABLE IF EXISTS entity_views;
CREATE TABLE IF NOT EXISTS entity_views (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
name varchar(255),
keys varchar(255),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);

5
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -48,6 +48,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -159,6 +160,10 @@ public class ActorSystemContext {
@Getter
private AuditLogService auditLogService;
@Autowired
@Getter
private EntityViewService entityViewService;
@Autowired
@Getter
private TelemetrySubscriptionService tsSubService;

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -41,6 +41,7 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
@ -212,6 +213,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getRelationService();
}
@Override
public EntityViewService getEntityViewService() {
return mainCtx.getEntityViewService();
}
@Override
public MailService getMailService() {
if (mainCtx.isAllowSystemMailService()) {

26
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -56,6 +56,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
@ -139,6 +140,9 @@ public abstract class BaseController {
@Autowired
protected DeviceStateService deviceStateService;
@Autowired
protected EntityViewService entityViewService;
@ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
errorResponseHandler.handle(ex, response);
@ -313,6 +317,9 @@ public abstract class BaseController {
case USER:
checkUserId(new UserId(entityId.getId()));
return;
case ENTITY_VIEW:
checkEntityViewId(new EntityViewId(entityId.getId()));
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
}
@ -340,6 +347,25 @@ public abstract class BaseController {
}
}
protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException {
try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityView entityView = entityViewService.findEntityViewById(entityViewId);
checkEntityView(entityView);
return entityView;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected void checkEntityView(EntityView entityView) throws ThingsboardException {
checkNotNull(entityView);
checkTenantId(entityView.getTenantId());
if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(entityView.getCustomerId());
}
}
Asset checkAssetId(AssetId assetId) throws ThingsboardException {
try {
validateId(assetId, "Incorrect assetId " + assetId);

218
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -0,0 +1,218 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.List;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
/**
* Created by Victor Basanets on 8/28/2017.
*/
@RestController
@RequestMapping("/api")
public class EntityViewController extends BaseController {
public static final String ENTITY_VIEW_ID = "entityViewId";
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET)
@ResponseBody
public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView", method = RequestMethod.POST)
@ResponseBody
public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException {
try {
entityView.setTenantId(getCurrentUser().getTenantId());
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
logEntityAction(savedEntityView.getId(), savedEntityView, null,
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null,
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteEntityView(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
EntityView entityView = checkEntityViewId(entityViewId);
entityViewService.deleteEntityView(entityViewId);
logEntityAction(entityViewId, entityView, entityView.getCustomerId(),
ActionType.DELETED,null, strEntityViewId);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW),
null,
null,
ActionType.DELETED, e, strEntityViewId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST)
@ResponseBody
public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId,
@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId);
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId);
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
checkEntityViewId(entityViewId);
EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId));
logEntityAction(entityViewId, savedEntityView,
savedEntityView.getCustomerId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName());
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseBody
public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
EntityView entityView = checkEntityViewId(entityViewId);
if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Entity View isn't assigned to any customer!");
}
Customer customer = checkCustomerId(entityView.getCustomerId());
EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId));
logEntityAction(entityViewId, entityView,
entityView.getCustomerId(),
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName());
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<EntityView> getCustomerEntityViews(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<EntityView> getTenantEntityViews(
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityViews", method = RequestMethod.POST)
@ResponseBody
public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkEntityId(query.getParameters().getEntityId());
try {
List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
entityViews = entityViews.stream().filter(entityView -> {
try {
checkEntityView(entityView);
return true;
} catch (ThingsboardException e) {
return false;
}
}).collect(Collectors.toList());
return entityViews;
} catch (Exception e) {
throw handleException(e);
}
}
}

21
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -24,9 +24,10 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.install.DataUpdateService;
import org.thingsboard.server.service.install.DatabaseSchemaService;
import org.thingsboard.server.service.install.DatabaseUpgradeService;
import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.service.install.TsDatabaseSchemaService;
@Service
@Profile("install")
@ -43,7 +44,10 @@ public class ThingsboardInstallService {
private Boolean loadDemo;
@Autowired
private DatabaseSchemaService databaseSchemaService;
private EntityDatabaseSchemaService entityDatabaseSchemaService;
@Autowired
private TsDatabaseSchemaService tsDatabaseSchemaService;
@Autowired
private DatabaseUpgradeService databaseUpgradeService;
@ -88,6 +92,11 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("1.4.0");
case "2.0.0":
log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ...");
databaseUpgradeService.upgradeDatabase("2.0.0");
log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts");
@ -114,9 +123,13 @@ public class ThingsboardInstallService {
log.info("Starting ThingsBoard Installation...");
log.info("Installing DataBase schema...");
log.info("Installing DataBase schema for entities...");
entityDatabaseSchemaService.createDatabaseSchema();
log.info("Installing DataBase schema for timeseries...");
databaseSchemaService.createDatabaseSchema();
tsDatabaseSchemaService.createDatabaseSchema();
log.info("Loading system data...");

19
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java

@ -17,24 +17,17 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.service.install.cql.CQLStatementsParser;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Service
@NoSqlDao
@Profile("install")
@Slf4j
public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
public abstract class CassandraAbstractDatabaseSchemaService implements DatabaseSchemaService {
private static final String CASSANDRA_DIR = "cassandra";
private static final String SCHEMA_CQL = "schema.cql";
@Autowired
private CassandraInstallCluster cluster;
@ -42,10 +35,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
@Autowired
private InstallScripts installScripts;
private final String schemaCql;
protected CassandraAbstractDatabaseSchemaService(String schemaCql) {
this.schemaCql = schemaCql;
}
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing Cassandra DataBase schema...");
Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL);
log.info("Installing Cassandra DataBase schema part: " + schemaCql);
Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, schemaCql);
loadCql(schemaFile);
}

30
application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.NoSqlDao;
@Service
@NoSqlDao
@Profile("install")
public class CassandraEntityDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
public CassandraEntityDatabaseSchemaService() {
super("schema-entities.cql");
}
}

30
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.NoSqlTsDao;
@Service
@NoSqlTsDao
@Profile("install")
public class CassandraTsDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
implements TsDatabaseSchemaService {
public CassandraTsDatabaseSchemaService() {
super("schema-ts.cql");
}
}

19
application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java

@ -0,0 +1,19 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
public interface EntityDatabaseSchemaService extends DatabaseSchemaService {
}

19
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java

@ -18,9 +18,6 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.SqlDao;
import java.nio.charset.Charset;
import java.nio.file.Files;
@ -29,14 +26,10 @@ import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
@Service
@Profile("install")
@Slf4j
@SqlDao
public class SqlDatabaseSchemaService implements DatabaseSchemaService {
public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService {
private static final String SQL_DIR = "sql";
private static final String SCHEMA_SQL = "schema.sql";
@Value("${spring.datasource.url}")
private String dbUrl;
@ -50,12 +43,18 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
@Autowired
private InstallScripts installScripts;
private final String schemaSql;
protected SqlAbstractDatabaseSchemaService(String schemaSql) {
this.schemaSql = schemaSql;
}
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing SQL DataBase schema...");
log.info("Installing SQL DataBase schema part: " + schemaSql);
Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL);
Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaSql);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema

9
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -107,6 +107,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
}
break;
case "2.0.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

30
application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.SqlDao;
@Service
@SqlDao
@Profile("install")
public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
public SqlEntityDatabaseSchemaService() {
super("schema-entities.sql");
}
}

30
application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.SqlTsDao;
@Service
@SqlTsDao
@Profile("install")
public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements TsDatabaseSchemaService {
public SqlTsDatabaseSchemaService() {
super("schema-ts.sql");
}
}

19
application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java

@ -0,0 +1,19 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
public interface TsDatabaseSchemaService extends DatabaseSchemaService {
}

31
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -26,6 +26,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -44,6 +46,7 @@ import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
@ -66,6 +69,7 @@ public class AccessValidator {
public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!";
@Autowired
protected TenantService tenantService;
@ -88,6 +92,9 @@ public class AccessValidator {
@Autowired
protected RuleChainService ruleChainService;
@Autowired
protected EntityViewService entityViewService;
private ExecutorService executor;
@PostConstruct
@ -158,6 +165,9 @@ public class AccessValidator {
case TENANT:
validateTenant(currentUser, entityId, callback);
return;
case ENTITY_VIEW:
validateEntityView(currentUser, entityId, callback);
return;
default:
//TODO: add support of other entities
throw new IllegalStateException("Not Implemented!");
@ -293,6 +303,27 @@ public class AccessValidator {
}
}
private void validateEntityView(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<EntityView> entityViewFuture = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId()));
Futures.addCallback(entityViewFuture, getCallback(callback, entityView -> {
if (entityView == null) {
return ValidationResult.entityNotFound(ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND);
} else {
if (!entityView.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Entity-view doesn't belong to the current Tenant!");
} else if (currentUser.isCustomerUser() && !entityView.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Entity-view doesn't belong to the current Customer!");
} else {
return ValidationResult.ok(entityView);
}
}
}), executor);
}
}
private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
return new FutureCallback<T>() {
@Override

69
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -29,9 +29,11 @@ import org.thingsboard.rule.engine.api.util.DonAsynchron;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
@ -48,6 +50,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
@ -64,6 +68,7 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -101,6 +106,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Autowired
private ClusterRpcService rpcService;
@Autowired
private EntityViewService entityViewService;
@Autowired
@Lazy
private DeviceStateService stateService;
@ -133,20 +141,64 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Override
public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) {
long startTime = 0L;
long endTime = 0L;
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
EntityView entityView = entityViewService.findEntityViewById(new EntityViewId(entityId.getId()));
entityId = entityView.getEntityId();
startTime = entityView.getStartTimeMs();
endTime = entityView.getEndTimeMs();
sub = getUpdatedSubscriptionState(entityId, sub, entityView);
}
Optional<ServerAddress> server = routingService.resolveById(entityId);
Subscription subscription;
if (server.isPresent()) {
ServerAddress address = server.get();
log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address);
subscription = new Subscription(sub, true, address);
log.trace("[{}] Forwarding subscription [{}] for [{}] entity [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId, address);
subscription = new Subscription(sub, true, address, startTime, endTime);
tellNewSubscription(address, sessionId, subscription);
} else {
log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId);
subscription = new Subscription(sub, true);
log.trace("[{}] Registering local subscription [{}] for [{}] entity [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId);
subscription = new Subscription(sub, true, null, startTime, endTime);
}
registerSubscription(sessionId, entityId, subscription);
}
private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) {
boolean allKeys;
Map<String, Long> keyStates;
if (sub.getType().equals(TelemetryFeature.TIMESERIES) && !entityView.getKeys().getTimeseries().isEmpty()) {
allKeys = false;
keyStates = sub.getKeyStates().entrySet()
.stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
} else if (sub.getType().equals(TelemetryFeature.ATTRIBUTES)) {
if (sub.getScope().equals(DataConstants.CLIENT_SCOPE) && !entityView.getKeys().getAttributes().getCs().isEmpty()) {
allKeys = false;
keyStates = filterMap(sub, entityView.getKeys().getAttributes().getCs());
} else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSs().isEmpty()) {
allKeys = false;
keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSs());
} else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSh().isEmpty()) {
allKeys = false;
keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSh());
} else {
allKeys = sub.isAllKeys();
keyStates = sub.getKeyStates();
}
} else {
allKeys = sub.isAllKeys();
keyStates = sub.getKeyStates();
}
return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), allKeys, keyStates, sub.getScope());
}
private Map<String, Long> filterMap(SubscriptionState sub, List<String> allowedKeys) {
return sub.getKeyStates().entrySet()
.stream().filter(entry -> allowedKeys.contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId) {
cleanupLocalWsSessionSubscriptions(sessionId);
@ -415,7 +467,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
onLocalSubUpdate(entityId, s -> TelemetryFeature.ATTRIBUTES == s.getType() && (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope())), s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (AttributeKvEntry kv : attributes) {
if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
if (isInTimeRange(s, kv.getLastUpdateTs()) && (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey()))) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
@ -430,7 +482,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
onLocalSubUpdate(entityId, s -> TelemetryFeature.TIMESERIES == s.getType(), s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (TsKvEntry kv : ts) {
if (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey()))) {
if (isInTimeRange(s, kv.getTs()) && (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
@ -441,6 +493,11 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
});
}
private boolean isInTimeRange(Subscription subscription, long kvTime) {
return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime)
&& (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime);
}
private void onLocalSubUpdate(EntityId entityId, Predicate<Subscription> filter, Function<Subscription, List<TsKvEntry>> f) {
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) {

6
application/src/main/java/org/thingsboard/server/service/telemetry/sub/Subscription.java

@ -30,9 +30,11 @@ public class Subscription {
private final SubscriptionState sub;
private final boolean local;
private ServerAddress server;
private long startTime;
private long endTime;
public Subscription(SubscriptionState sub, boolean local) {
this(sub, local, null);
public Subscription(SubscriptionState sub, boolean local, ServerAddress server) {
this(sub, local, server, 0L, 0L);
}
public String getWsSessionId() {

13
application/src/main/resources/thingsboard.yml

@ -159,7 +159,11 @@ quota:
intervalMin: 2
database:
type: "${DATABASE_TYPE:sql}" # cassandra OR sql
entities:
type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql
ts:
type: "${DATABASE_TS_TYPE:sql}" # cassandra OR sql (for hybrid mode, only this value should be cassandra)
# Cassandra driver configuration parameters
cassandra:
@ -206,7 +210,7 @@ cassandra:
write_consistency_level: "${CASSANDRA_WRITE_CONSISTENCY_LEVEL:ONE}"
default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}"
# Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS,INDEFINITE
ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
ts_key_value_partitioning: "${TS_KV_PARTITIONING:INDEFINITE}"
ts_key_value_ttl: "${TS_KV_TTL:0}"
buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}"
@ -289,6 +293,9 @@ caffeine:
assets:
timeToLiveInMinutes: 1440
maxSize: 100000
entityViews:
timeToLiveInMinutes: 1440
maxSize: 100000
redis:
# standalone or cluster
@ -350,7 +357,7 @@ spring:
password: "${SPRING_DATASOURCE_PASSWORD:}"
# PostgreSQL DAO Configuration
#spring:
# spring:
# data:
# sql:
# repositories:

456
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -0,0 +1,456 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.objects.AttributesEntityView;
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public abstract class BaseEntityViewControllerTest extends AbstractControllerTest {
private IdComparator<EntityView> idComparator;
private Tenant savedTenant;
private User tenantAdmin;
private Device testDevice;
private TelemetryEntityView telemetry;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
idComparator = new IdComparator<>();
savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Device device = new Device();
device.setName("Test device");
device.setType("default");
testDevice = doPost("/api/device", device, Device.class);
telemetry = new TelemetryEntityView(
Arrays.asList("109", "209", "309"),
new AttributesEntityView(
Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"),
Arrays.asList("saValue1", "saValue2", "saValue3", "saValue4"),
Arrays.asList("shValue1", "shValue2", "shValue3", "shValue4")));
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindEntityViewById() throws Exception {
EntityView savedView = getNewSavedEntityView("Test entity view");
EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
Assert.assertNotNull(foundView);
assertEquals(savedView, foundView);
}
@Test
public void testSaveEntityView() throws Exception {
EntityView savedView = getNewSavedEntityView("Test entity view");
Assert.assertNotNull(savedView);
Assert.assertNotNull(savedView.getId());
Assert.assertTrue(savedView.getCreatedTime() > 0);
assertEquals(savedTenant.getId(), savedView.getTenantId());
Assert.assertNotNull(savedView.getCustomerId());
assertEquals(NULL_UUID, savedView.getCustomerId().getId());
assertEquals(savedView.getName(), savedView.getName());
savedView.setName("New test entity view");
doPost("/api/entityView", savedView, EntityView.class);
EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(foundEntityView.getName(), savedView.getName());
assertEquals(foundEntityView.getKeys(), telemetry);
}
@Test
public void testDeleteEntityView() throws Exception {
EntityView view = getNewSavedEntityView("Test entity view");
Customer customer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class);
view.setCustomerId(customer.getId());
EntityView savedView = doPost("/api/entityView", view, EntityView.class);
doDelete("/api/entityView/" + savedView.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/entityView/" + savedView.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testSaveEntityViewWithEmptyName() throws Exception {
doPost("/api/entityView", new EntityView())
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Entity view name should be specified!")));
}
@Test
public void testAssignAndUnAssignedEntityViewToCustomer() throws Exception {
EntityView view = getNewSavedEntityView("Test entity view");
Customer savedCustomer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class);
view.setCustomerId(savedCustomer.getId());
EntityView savedView = doPost("/api/entityView", view, EntityView.class);
EntityView assignedView = doPost(
"/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString(),
EntityView.class);
assertEquals(savedCustomer.getId(), assignedView.getCustomerId());
EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(savedCustomer.getId(), foundView.getCustomerId());
EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(ModelConstants.NULL_UUID, unAssignedView.getCustomerId().getId());
foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId());
}
@Test
public void testAssignEntityViewToNonExistentCustomer() throws Exception {
EntityView savedView = getNewSavedEntityView("Test entity view");
doPost("/api/customer/" + UUIDs.timeBased().toString() + "/device/" + savedView.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testAssignEntityViewToCustomerFromDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant2 = getNewTenant("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin2.setTenantId(savedTenant2.getId());
tenantAdmin2.setEmail("tenant3@thingsboard.org");
tenantAdmin2.setFirstName("Joe");
tenantAdmin2.setLastName("Downs");
createUserAndLogin(tenantAdmin2, "testPassword1");
Customer customer = getNewCustomer("Different customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
login(tenantAdmin.getEmail(), "testPassword1");
EntityView savedView = getNewSavedEntityView("Test entity view");
doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString())
.andExpect(status().isForbidden());
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testGetCustomerEntityViews() throws Exception {
CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId();
String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?";
List<EntityView> views = new ArrayList<>();
for (int i = 0; i < 128; i++) {
views.add(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/"
+ getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class));
}
List<EntityView> loadedViews = loadListOf(new TextPageLink(23), urlTemplate);
Collections.sort(views, idComparator);
Collections.sort(loadedViews, idComparator);
assertEquals(views, loadedViews);
}
@Test
public void testGetCustomerEntityViewsByName() throws Exception {
CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId();
String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?";
String name1 = "Entity view name1";
List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString()
+ "/entityView/");
List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate);
Collections.sort(namesOfView1, idComparator);
Collections.sort(loadedNamesOfView1, idComparator);
assertEquals(namesOfView1, loadedNamesOfView1);
String name2 = "Entity view name2";
List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString()
+ "/entityView/");
List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate);
Collections.sort(NamesOfView2, idComparator);
Collections.sort(loadedNamesOfView2, idComparator);
assertEquals(NamesOfView2, loadedNamesOfView2);
for (EntityView view : loadedNamesOfView1) {
doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
}
TextPageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate,
new TypeReference<TextPageData<EntityView>>() {
}, new TextPageLink(4, name1));
Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size());
for (EntityView view : loadedNamesOfView2) {
doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
}
pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() {
},
new TextPageLink(4, name2));
Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size());
}
@Test
public void testGetTenantEntityViews() throws Exception {
List<EntityView> views = new ArrayList<>();
for (int i = 0; i < 178; i++) {
views.add(getNewSavedEntityView("Test entity view" + i));
}
List<EntityView> loadedViews = loadListOf(new TextPageLink(23), "/api/tenant/entityViews?");
Collections.sort(views, idComparator);
Collections.sort(loadedViews, idComparator);
assertEquals(views, loadedViews);
}
@Test
public void testGetTenantEntityViewsByName() throws Exception {
String name1 = "Entity view name1";
List<EntityView> namesOfView1 = fillListOf(143, name1);
List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), "/api/tenant/entityViews?");
Collections.sort(namesOfView1, idComparator);
Collections.sort(loadedNamesOfView1, idComparator);
assertEquals(namesOfView1, loadedNamesOfView1);
String name2 = "Entity view name2";
List<EntityView> NamesOfView2 = fillListOf(75, name2);
List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), "/api/tenant/entityViews?");
Collections.sort(NamesOfView2, idComparator);
Collections.sort(loadedNamesOfView2, idComparator);
assertEquals(NamesOfView2, loadedNamesOfView2);
for (EntityView view : loadedNamesOfView1) {
doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
}
TextPageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?",
new TypeReference<TextPageData<EntityView>>() {
}, new TextPageLink(4, name1));
Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size());
for (EntityView view : loadedNamesOfView2) {
doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
}
pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference<TextPageData<EntityView>>() {
},
new TextPageLink(4, name2));
Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size());
}
@Test
public void testTheCopyOfAttrsIntoTSForTheView() throws Exception {
Set<String> actualAttributesSet =
getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
Set<String> expectedActualAttributesSet =
new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
Thread.sleep(1000);
EntityView savedView = getNewSavedEntityView("Test entity view");
List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
assertEquals("value1", getValue(values, "caValue1"));
assertEquals(true, getValue(values, "caValue2"));
assertEquals(42.0, getValue(values, "caValue3"));
assertEquals(73, getValue(values, "caValue4"));
}
@Test
public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception {
Set<String> actualAttributesSet =
getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
Thread.sleep(1000);
List<Map<String, Object>> valueTelemetryOfDevices = doGetAsync("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
EntityView view = new EntityView();
view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId());
view.setName("Test entity view");
view.setKeys(telemetry);
view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
EntityView savedView = doPost("/api/entityView", view, EntityView.class);
List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
assertEquals(0, values.size());
}
private Set<String> getAttributesByKeys(String stringKV) throws Exception {
String viewDeviceId = testDevice.getId().getId().toString();
DeviceCredentials deviceCredentials =
doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class);
assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options);
Thread.sleep(3000);
MqttMessage message = new MqttMessage();
message.setPayload((stringKV).getBytes());
client.publish("v1/devices/me/attributes", message);
Thread.sleep(1000);
return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class));
}
private Object getValue(List<Map<String, Object>> values, String stringValue) {
return values.size() == 0 ? null :
values.stream()
.filter(value -> value.get("key").equals(stringValue))
.findFirst().get().get("value");
}
private EntityView getNewSavedEntityView(String name) throws Exception {
EntityView view = new EntityView();
view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId());
view.setName(name);
view.setKeys(telemetry);
return doPost("/api/entityView", view, EntityView.class);
}
private Customer getNewCustomer(String title) {
Customer customer = new Customer();
customer.setTitle(title);
return customer;
}
private Tenant getNewTenant(String title) {
Tenant tenant = new Tenant();
tenant.setTitle(title);
return tenant;
}
private List<EntityView> fillListOf(int limit, String partOfName, String urlTemplate) throws Exception {
List<EntityView> views = new ArrayList<>();
for (EntityView view : fillListOf(limit, partOfName)) {
views.add(doPost(urlTemplate + view.getId().getId().toString(), EntityView.class));
}
return views;
}
private List<EntityView> fillListOf(int limit, String partOfName) throws Exception {
List<EntityView> viewNames = new ArrayList<>();
for (int i = 0; i < limit; i++) {
String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15);
fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase();
EntityView view = getNewSavedEntityView(fullName);
Customer customer = getNewCustomer("Test customer " + String.valueOf(Math.random()));
view.setCustomerId(doPost("/api/customer", customer, Customer.class).getId());
viewNames.add(doPost("/api/entityView", view, EntityView.class));
}
return viewNames;
}
private List<EntityView> loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception {
List<EntityView> loadedItems = new ArrayList<>();
TextPageData<EntityView> pageData;
do {
pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() {
}, pageLink);
loadedItems.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return loadedItems;
}
}

3
application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java

@ -32,7 +32,8 @@ public class ControllerNoSqlTestSuite {
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)),
"cassandra-test.yaml", 30000l);

4
application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java

@ -24,13 +24,13 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.controller.sql.*SqlTest",
"org.thingsboard.server.controller.sql.*Test",
})
public class ControllerSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql",
"sql-test.properties");
}

26
application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.nosql;
import org.thingsboard.server.controller.BaseEntityViewControllerTest;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
/**
* Created by Victor Basanets on 8/27/2017.
*/
@DaoNoSqlTest
public class EntityViewControllerNoSqlTest extends BaseEntityViewControllerTest {
}

31
application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.controller.BaseEntityViewControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Arrays;
/**
* Created by Victor Basanets on 8/27/2017.
*/
@DaoSqlTest
public class EntityViewControllerSqlTest extends BaseEntityViewControllerTest {
}

3
application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java

@ -32,7 +32,8 @@ public class MqttNoSqlTestSuite {
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
"cassandra-test.yaml", 30000l);
}

4
application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java

@ -15,11 +15,9 @@
*/
package org.thingsboard.server.mqtt;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomCassandraCQLUnit;
import org.thingsboard.server.dao.CustomSqlUnit;
import java.util.Arrays;
@ -31,7 +29,7 @@ public class MqttSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql",
"sql-test.properties");
}

3
application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java

@ -35,7 +35,8 @@ public class RuleEngineNoSqlTestSuite {
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
"cassandra-test.yaml", 30000l);

2
application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java

@ -30,7 +30,7 @@ public class RuleEngineSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql",
"sql-test.properties");
}

3
application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java

@ -34,7 +34,8 @@ public class SystemNoSqlTestSuite {
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
"cassandra-test.yaml", 30000l);
}

2
application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java

@ -31,7 +31,7 @@ public class SystemSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql",
"sql-test.properties");

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -20,4 +20,5 @@ public class CacheConstants {
public static final String RELATIONS_CACHE = "relations";
public static final String DEVICE_CACHE = "devices";
public static final String ASSET_CACHE = "assets";
public static final String ENTITY_VIEW_CACHE = "entityViews";
}

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE;
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW
}

78
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -0,0 +1,78 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
/**
* Created by Victor Basanets on 8/27/2017.
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
implements HasName, HasTenantId, HasCustomerId {
private static final long serialVersionUID = 5582010124562018986L;
private EntityId entityId;
private TenantId tenantId;
private CustomerId customerId;
private String name;
private TelemetryEntityView keys;
private long startTimeMs;
private long endTimeMs;
public EntityView() {
super();
}
public EntityView(EntityViewId id) {
super(id);
}
public EntityView(EntityView entityView) {
super(entityView);
}
@Override
public String getSearchText() {
return getName() /*What the ...*/;
}
@Override
public CustomerId getCustomerId() {
return customerId;
}
@Override
public String getName() {
return name;
}
@Override
public TenantId getTenantId() {
return tenantId;
}
}

42
common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.entityview;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
import java.util.List;
@Data
public class EntityViewSearchQuery {
private RelationsSearchParameters parameters;
private String relationType;
public EntityRelationsQuery toEntitySearchQuery() {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.ENTITY_VIEW))));
return query;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -57,6 +57,8 @@ public class EntityIdFactory {
return new RuleChainId(uuid);
case RULE_NODE:
return new RuleNodeId(uuid);
case ENTITY_VIEW:
return new EntityViewId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}

44
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
/**
* Created by Victor Basanets on 8/27/2017.
*/
public class EntityViewId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public EntityViewId(@JsonProperty("id") UUID id) {
super(id);
}
public static EntityViewId fromString(String entityViewID) {
return new EntityViewId(UUID.fromString(entityViewID));
}
@Override
public EntityType getEntityType() {
return EntityType.ENTITY_VIEW;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java

@ -42,4 +42,8 @@ public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
this(key, startTs, endTs, endTs - startTs, 1, Aggregation.AVG, "DESC");
}
public BaseReadTsKvQuery(String key, long startTs, long endTs, int limit, String orderBy) {
this(key, startTs, endTs, endTs - startTs, limit, Aggregation.NONE, orderBy);
}
}

47
common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.objects;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Victor Basanets on 9/05/2017.
*/
@Data
@NoArgsConstructor
public class AttributesEntityView {
private List<String> cs = new ArrayList<>();
private List<String> ss = new ArrayList<>();
private List<String> sh = new ArrayList<>();
public AttributesEntityView(List<String> cs,
List<String> ss,
List<String> sh) {
this.cs = new ArrayList<>(cs);
this.ss = new ArrayList<>(ss);
this.sh = new ArrayList<>(sh);
}
public AttributesEntityView(AttributesEntityView obj) {
this(obj.getCs(), obj.getSs(), obj.getSh());
}
}

43
common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.objects;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Victor Basanets on 9/05/2017.
*/
@Data
@NoArgsConstructor
public class TelemetryEntityView {
private List<String> timeseries;
private AttributesEntityView attributes;
public TelemetryEntityView(List<String> timeseries, AttributesEntityView attributes) {
this.timeseries = new ArrayList<>(timeseries);
this.attributes = attributes;
}
public TelemetryEntityView(TelemetryEntityView obj) {
this(obj.getTimeseries(), obj.getAttributes());
}
}

17
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
@ -43,6 +44,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@ -76,6 +78,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
@Autowired
private CustomerDao customerDao;
@Autowired
private EntityViewService entityViewService;
@Autowired
private CacheManager cacheManager;
@ -130,11 +135,21 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
validateId(assetId, INCORRECT_ASSET_ID + assetId);
deleteEntityRelations(assetId);
Cache cache = cacheManager.getCache(ASSET_CACHE);
Asset asset = assetDao.findById(assetId.getId());
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete asset that is assigned to entity views!");
}
} catch (Exception e) {
log.error("Exception while finding entity views for assetId [{}]", assetId, e);
throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
}
List<Object> list = new ArrayList<>();
list.add(asset.getTenantId());
list.add(asset.getName());
Cache cache = cacheManager.getCache(ASSET_CACHE);
cache.evict(list);
assetDao.removeById(assetId.getId());

4
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java

@ -17,12 +17,12 @@ package org.thingsboard.server.dao.cassandra;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import javax.annotation.PostConstruct;
@Component
@NoSqlDao
@NoSqlAnyDao
public class CassandraCluster extends AbstractCassandraCluster {
@Value("${cassandra.keyspace_name}")

4
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java

@ -17,12 +17,12 @@ package org.thingsboard.server.dao.cassandra;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import javax.annotation.PostConstruct;
@Component
@NoSqlDao
@NoSqlAnyDao
@Profile("install")
public class CassandraInstallCluster extends AbstractCassandraCluster {

4
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraQueryOptions.java

@ -21,14 +21,14 @@ import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import javax.annotation.PostConstruct;
@Component
@Configuration
@Data
@NoSqlDao
@NoSqlAnyDao
public class CassandraQueryOptions {
@Value("${cassandra.query.default_fetch_size}")

4
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraSocketOptions.java

@ -20,14 +20,14 @@ import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import javax.annotation.PostConstruct;
@Component
@Configuration
@Data
@NoSqlDao
@NoSqlAnyDao
public class CassandraSocketOptions {
@Value("${cassandra.socket.connect_timeout}")

5
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -32,6 +32,7 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
@ -69,6 +70,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Autowired
private DeviceService deviceService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private DashboardService dashboardService;
@ -111,6 +115,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
throw new IncorrectParameterException("Unable to delete non-existent customer.");
}
dashboardService.unassignCustomerDashboards(customerId);
entityViewService.unassignCustomerEntityViews(customer.getTenantId(), customerId);
assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
userService.deleteCustomerUsers(customer.getTenantId(), customerId);

22
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@ -86,6 +88,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Autowired
private DeviceCredentialsService deviceCredentialsService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private CacheManager cacheManager;
@ -145,18 +150,31 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Override
public void deleteDevice(DeviceId deviceId) {
log.trace("Executing deleteDevice [{}]", deviceId);
Cache cache = cacheManager.getCache(DEVICE_CACHE);
validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
Device device = deviceDao.findById(deviceId.getId());
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete device that is assigned to entity views!");
}
} catch (Exception e) {
log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e);
}
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId);
if (deviceCredentials != null) {
deviceCredentialsService.deleteDeviceCredentials(deviceCredentials);
}
deleteEntityRelations(deviceId);
Device device = deviceDao.findById(deviceId.getId());
List<Object> list = new ArrayList<>();
list.add(device.getTenantId());
list.add(device.getName());
Cache cache = cacheManager.getCache(DEVICE_CACHE);
cache.evict(list);
deviceDao.removeById(deviceId.getId());
}

121
dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java

@ -0,0 +1,121 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entityview;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.nosql.EntityViewEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.util.NoSqlDao;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY;
/**
* Created by Victor Basanets on 9/06/2017.
*/
@Component
@Slf4j
@NoSqlDao
public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<EntityViewEntity, EntityView> implements EntityViewDao {
@Override
protected Class<EntityViewEntity> getColumnFamilyClass() {
return EntityViewEntity.class;
}
@Override
protected String getColumnFamilyName() {
return ENTITY_VIEW_TABLE_FAMILY_NAME;
}
@Override
public EntityView save(EntityView domain) {
EntityView savedEntityView = super.save(domain);
EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW,
savedEntityView.getId().getEntityType().toString());
EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype);
Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity);
executeWrite(saveStatement);
return savedEntityView;
}
@Override
public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<EntityViewEntity> entityViewEntities =
findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]",
entityViewEntities, tenantId, pageLink);
return DaoUtil.convertDataList(entityViewEntities);
}
@Override
public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where();
query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId));
query.and(eq(ENTITY_VIEW_NAME_PROPERTY, name));
return Optional.ofNullable(DaoUtil.getData(findOneByStatement(query)));
}
@Override
public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
log.debug("Try to find entity views by tenantId [{}], customerId[{}] and pageLink [{}]",
tenantId, customerId, pageLink);
List<EntityViewEntity> entityViewEntities = findPageWithTextSearch(
ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF,
Arrays.asList(eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)),
pageLink);
log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}] and pageLink [{}]",
entityViewEntities, tenantId, customerId, pageLink);
return DaoUtil.convertDataList(entityViewEntities);
}
@Override
public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId);
Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF).where();
query.and(eq(TENANT_ID_PROPERTY, tenantId));
query.and(eq(ENTITY_ID_COLUMN, entityId));
return findListByStatementAsync(query);
}
}

73
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java

@ -0,0 +1,73 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entityview;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* Created by Victor Basanets on 8/28/2017.
*/
public interface EntityViewDao extends Dao<EntityView> {
/**
* Save or update device object
*
* @param entityView the entity-view object
* @return saved entity-view object
*/
EntityView save(EntityView entityView);
/**
* Find entity views by tenantId and page link.
*
* @param tenantId the tenantId
* @param pageLink the page link
* @return the list of entity view objects
*/
List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find entity views by tenantId and entity view name.
*
* @param tenantId the tenantId
* @param name the entity view name
* @return the optional entity view object
*/
Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name);
/**
* Find entity views by tenantId, customerId and page link.
*
* @param tenantId the tenantId
* @param customerId the customerId
* @param pageLink the page link
* @return the list of entity view objects
*/
List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId,
UUID customerId,
TextPageLink pageLink);
ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId);
}

58
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -0,0 +1,58 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entityview;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import java.util.List;
/**
* Created by Victor Basanets on 8/27/2017.
*/
public interface EntityViewService {
EntityView saveEntityView(EntityView entityView);
EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId);
EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId);
void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId);
EntityView findEntityViewById(EntityViewId entityViewId);
TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId);
void deleteEntityView(EntityViewId entityViewId);
void deleteEntityViewsByTenantId(TenantId tenantId);
}

356
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -0,0 +1,356 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entityview;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE;
import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
/**
* Created by Victor Basanets on 8/28/2017.
*/
@Service
@Slf4j
public class EntityViewServiceImpl extends AbstractEntityService implements EntityViewService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId ";
@Autowired
private EntityViewDao entityViewDao;
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@Autowired
private AttributesService attributesService;
@Autowired
private CacheManager cacheManager;
@Caching(evict = {
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"),
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")})
@Override
public EntityView saveEntityView(EntityView entityView) {
log.trace("Executing save entity view [{}]", entityView);
entityViewValidator.validate(entityView);
EntityView savedEntityView = entityViewDao.save(entityView);
List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
if (savedEntityView.getKeys() != null) {
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh()));
}
for (ListenableFuture<List<Void>> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to copy attributes to entity view", e);
throw new RuntimeException("Failed to copy attributes to entity view", e);
}
}
return savedEntityView;
}
@Override
public EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId) {
EntityView entityView = findEntityViewById(entityViewId);
entityView.setCustomerId(customerId);
return saveEntityView(entityView);
}
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override
public EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId) {
EntityView entityView = findEntityViewById(entityViewId);
entityView.setCustomerId(null);
return saveEntityView(entityView);
}
@Override
public void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId) {
log.trace("Executing unassignCustomerEntityViews, tenantId [{}], customerId [{}]", tenantId, customerId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
new CustomerEntityViewsUnAssigner(tenantId).removeEntities(customerId);
}
@Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override
public EntityView findEntityViewById(EntityViewId entityViewId) {
log.trace("Executing findEntityViewById [{}]", entityViewId);
validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
return entityViewDao.findById(entityViewId.getId());
}
@Override
public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) {
log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink);
return new TextPageData<>(entityViews, pageLink);
}
@Override
public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
TextPageLink pageLink) {
log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," +
" pageLink [{}]", tenantId, customerId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(),
customerId.getId(), pageLink);
return new TextPageData<>(entityViews, pageLink);
}
@Override
public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
ListenableFuture<List<EntityView>> entityViews = Futures.transformAsync(relations, r -> {
EntitySearchDirection direction = query.toEntitySearchQuery().getParameters().getDirection();
List<ListenableFuture<EntityView>> futures = new ArrayList<>();
for (EntityRelation relation : r) {
EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom();
if (entityId.getEntityType() == EntityType.ENTITY_VIEW) {
futures.add(findEntityViewByIdAsync(new EntityViewId(entityId.getId())));
}
}
return Futures.successfulAsList(futures);
});
return entityViews;
}
@Override
public ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId) {
log.trace("Executing findEntityViewById [{}]", entityViewId);
validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
return entityViewDao.findByIdAsync(entityViewId.getId());
}
@Override
public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) {
log.trace("Executing findEntityViewsByTenantIdAndEntityIdAsync, tenantId [{}], entityId [{}]", tenantId, entityId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(entityId.getId(), "Incorrect entityId" + entityId);
List<Object> tenantIdAndEntityId = new ArrayList<>();
tenantIdAndEntityId.add(tenantId);
tenantIdAndEntityId.add(entityId);
Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE);
List<EntityView> fromCache = cache.get(tenantIdAndEntityId, List.class);
if (fromCache != null) {
return Futures.immediateFuture(fromCache);
} else {
ListenableFuture<List<EntityView>> entityViewsFuture = entityViewDao.findEntityViewsByTenantIdAndEntityIdAsync(tenantId.getId(), entityId.getId());
Futures.addCallback(entityViewsFuture,
new FutureCallback<List<EntityView>>() {
@Override
public void onSuccess(@Nullable List<EntityView> result) {
cache.putIfAbsent(tenantIdAndEntityId, result);
}
@Override
public void onFailure(Throwable t) {
log.error("Error while finding entity views by tenantId and entityId", t);
}
});
return entityViewsFuture;
}
}
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override
public void deleteEntityView(EntityViewId entityViewId) {
log.trace("Executing deleteEntityView [{}]", entityViewId);
validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
deleteEntityRelations(entityViewId);
EntityView entityView = entityViewDao.findById(entityViewId.getId());
cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId()));
entityViewDao.removeById(entityViewId.getId());
}
@Override
public void deleteEntityViewsByTenantId(TenantId tenantId) {
log.trace("Executing deleteEntityViewsByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
tenantEntityViewRemover.removeEntities(tenantId);
}
private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
if (keys != null && !keys.isEmpty()) {
ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
return Futures.transform(getAttrFuture, attributeKvEntries -> {
List<AttributeKvEntry> filteredAttributes = new ArrayList<>();
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
filteredAttributes =
attributeKvEntries.stream()
.filter(attributeKvEntry -> {
long startTime = entityView.getStartTimeMs();
long endTime = entityView.getEndTimeMs();
long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
return startTime == 0 && endTime == 0 ||
(endTime == 0 && startTime < lastUpdateTs) ||
(startTime == 0 && endTime > lastUpdateTs)
? true : startTime < lastUpdateTs && endTime > lastUpdateTs;
}).collect(Collectors.toList());
}
try {
return attributesService.save(entityView.getId(), scope, filteredAttributes).get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to copy attributes to entity view", e);
throw new RuntimeException("Failed to copy attributes to entity view", e);
}
});
} else {
return Futures.immediateFuture(null);
}
}
private DataValidator<EntityView> entityViewValidator =
new DataValidator<EntityView>() {
@Override
protected void validateCreate(EntityView entityView) {
entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
.ifPresent(e -> {
throw new DataValidationException("Entity view with such name already exists!");
});
}
@Override
protected void validateUpdate(EntityView entityView) {
entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
.ifPresent(e -> {
if (!e.getUuidId().equals(entityView.getUuidId())) {
throw new DataValidationException("Entity view with such name already exists!");
}
});
}
@Override
protected void validateDataImpl(EntityView entityView) {
if (StringUtils.isEmpty(entityView.getName())) {
throw new DataValidationException("Entity view name should be specified!");
}
if (entityView.getTenantId() == null) {
throw new DataValidationException("Entity view should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(entityView.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Entity view is referencing to non-existent tenant!");
}
}
if (entityView.getCustomerId() == null) {
entityView.setCustomerId(new CustomerId(NULL_UUID));
} else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) {
Customer customer = customerDao.findById(entityView.getCustomerId().getId());
if (customer == null) {
throw new DataValidationException("Can't assign entity view to non-existent customer!");
}
if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) {
throw new DataValidationException("Can't assign entity view to customer from different tenant!");
}
}
}
};
private PaginatedRemover<TenantId, EntityView> tenantEntityViewRemover =
new PaginatedRemover<TenantId, EntityView>() {
@Override
protected List<EntityView> findEntities(TenantId id, TextPageLink pageLink) {
return entityViewDao.findEntityViewsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(EntityView entity) {
deleteEntityView(new EntityViewId(entity.getUuidId()));
}
};
private class CustomerEntityViewsUnAssigner extends PaginatedRemover<CustomerId, EntityView> {
private TenantId tenantId;
CustomerEntityViewsUnAssigner(TenantId tenantId) {
this.tenantId = tenantId;
}
@Override
protected List<EntityView> findEntities(CustomerId id, TextPageLink pageLink) {
return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
}
@Override
protected void removeEntity(EntityView entity) {
unassignEntityViewFromCustomer(new EntityViewId(entity.getUuidId()));
}
}
}

20
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -52,7 +52,6 @@ public class ModelConstants {
public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key";
public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts";
/**
* Cassandra user constants.
*/
@ -130,12 +129,12 @@ public class ModelConstants {
* Cassandra device constants.
*/
public static final String DEVICE_COLUMN_FAMILY_NAME = "device";
public static final String DEVICE_FAMILY_NAME = "device";
public static final String DEVICE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String DEVICE_NAME_PROPERTY = "name";
public static final String DEVICE_TYPE_PROPERTY = "type";
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
@ -143,6 +142,23 @@ public class ModelConstants {
public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name";
public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant";
/**
* Cassandra entityView constants.
*/
public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views";
public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY;
public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer";
public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id";
public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts";
public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts";
public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text";
public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
/**
* Cassandra audit log constants.
*/

157
dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java

@ -0,0 +1,157 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.nosql;
import com.datastax.driver.core.utils.UUIDs;
import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Type;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import java.io.IOException;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
/**
* Created by Victor Basanets on 8/31/2017.
*/
@Data
@Table(name = ENTITY_VIEW_TABLE_FAMILY_NAME)
@EqualsAndHashCode
@ToString
@Slf4j
public class EntityViewEntity implements SearchTextEntity<EntityView> {
@PartitionKey(value = 0)
@Column(name = ID_PROPERTY)
private UUID id;
@Enumerated(EnumType.STRING)
@Column(name = ENTITY_TYPE_PROPERTY)
private EntityType entityType;
@PartitionKey(value = 1)
@Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
private UUID entityId;
@Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY)
private String keys;
@Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY)
private long startTs;
@Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY)
private long endTs;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
private static final ObjectMapper mapper = new ObjectMapper();
public EntityViewEntity() {
super();
}
public EntityViewEntity(EntityView entityView) {
if (entityView.getId() != null) {
this.id = entityView.getId().getId();
}
if (entityView.getEntityId() != null) {
this.entityId = entityView.getEntityId().getId();
this.entityType = entityView.getEntityId().getEntityType();
}
if (entityView.getTenantId() != null) {
this.tenantId = entityView.getTenantId().getId();
}
if (entityView.getCustomerId() != null) {
this.customerId = entityView.getCustomerId().getId();
}
this.name = entityView.getName();
try {
this.keys = mapper.writeValueAsString(entityView.getKeys());
} catch (IOException e) {
log.error("Unable to serialize entity view keys!", e);
}
this.startTs = entityView.getStartTimeMs();
this.endTs = entityView.getEndTimeMs();
this.searchText = entityView.getSearchText();
this.additionalInfo = entityView.getAdditionalInfo();
}
@Override
public String getSearchTextSource() {
return name;
}
@Override
public EntityView toData() {
EntityView entityView = new EntityView(new EntityViewId(id));
entityView.setCreatedTime(UUIDs.unixTimestamp(id));
if (entityId != null) {
entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), entityId.toString()));
}
if (tenantId != null) {
entityView.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
entityView.setCustomerId(new CustomerId(customerId));
}
entityView.setName(name);
try {
entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
} catch (IOException e) {
log.error("Unable to read entity view keys!", e);
}
entityView.setStartTimeMs(startTs);
entityView.setEndTimeMs(endTs);
entityView.setAdditionalInfo(additionalInfo);
return entityView;
}
}

158
dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java

@ -0,0 +1,158 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import java.io.IOException;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
/**
* Created by Victor Basanets on 8/30/2017.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME)
@Slf4j
public class EntityViewEntity extends BaseSqlEntity<EntityView> implements SearchTextEntity<EntityView> {
@Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
private String entityId;
@Enumerated(EnumType.STRING)
@Column(name = ENTITY_TYPE_PROPERTY)
private EntityType entityType;
@Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY)
private String tenantId;
@Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY)
private String keys;
@Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY)
private long startTs;
@Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY)
private long endTs;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
private static final ObjectMapper mapper = new ObjectMapper();
public EntityViewEntity() {
super();
}
public EntityViewEntity(EntityView entityView) {
if (entityView.getId() != null) {
this.setId(entityView.getId().getId());
}
if (entityView.getEntityId() != null) {
this.entityId = toString(entityView.getEntityId().getId());
this.entityType = entityView.getEntityId().getEntityType();
}
if (entityView.getTenantId() != null) {
this.tenantId = toString(entityView.getTenantId().getId());
}
if (entityView.getCustomerId() != null) {
this.customerId = toString(entityView.getCustomerId().getId());
}
this.name = entityView.getName();
try {
this.keys = mapper.writeValueAsString(entityView.getKeys());
} catch (IOException e) {
log.error("Unable to serialize entity view keys!", e);
}
this.startTs = entityView.getStartTimeMs();
this.endTs = entityView.getEndTimeMs();
this.searchText = entityView.getSearchText();
this.additionalInfo = entityView.getAdditionalInfo();
}
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
@Override
public EntityView toData() {
EntityView entityView = new EntityView(new EntityViewId(getId()));
entityView.setCreatedTime(UUIDs.unixTimestamp(getId()));
if (entityId != null) {
entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString()));
}
if (tenantId != null) {
entityView.setTenantId(new TenantId(toUUID(tenantId)));
}
if (customerId != null) {
entityView.setCustomerId(new CustomerId(toUUID(customerId)));
}
entityView.setName(name);
try {
entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
} catch (IOException e) {
log.error("Unable to read entity view keys!", e);
}
entityView.setStartTimeMs(startTs);
entityView.setEndTimeMs(endTs);
entityView.setAdditionalInfo(additionalInfo);
return entityView;
}
}

56
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.entityview;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.model.sql.EntityViewEntity;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
/**
* Created by Victor Basanets on 8/31/2017.
*/
@SqlDao
public interface EntityViewRepository extends CrudRepository<EntityViewEntity, String> {
@Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
"AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
"AND e.id > :idOffset ORDER BY e.id")
List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
@Param("textSearch") String textSearch,
@Param("idOffset") String idOffset,
Pageable pageable);
@Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
"AND e.customerId = :customerId " +
"AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
"AND e.id > :idOffset ORDER BY e.id")
List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
@Param("customerId") String customerId,
@Param("searchText") String searchText,
@Param("idOffset") String idOffset,
Pageable pageable);
EntityViewEntity findByTenantIdAndName(String tenantId, String name);
List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId);
}

103
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java

@ -0,0 +1,103 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.entityview;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.entityview.EntityViewDao;
import org.thingsboard.server.dao.model.sql.EntityViewEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
/**
* Created by Victor Basanets on 8/31/2017.
*/
@Component
@SqlDao
public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity, EntityView>
implements EntityViewDao {
@Autowired
private EntityViewRepository entityViewRepository;
@Override
protected Class<EntityViewEntity> getEntityClass() {
return EntityViewEntity.class;
}
@Override
protected CrudRepository<EntityViewEntity, String> getCrudRepository() {
return entityViewRepository;
}
@Override
public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
return DaoUtil.convertDataList(
entityViewRepository.findByTenantId(
fromTimeUUID(tenantId),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
}
@Override
public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
return Optional.ofNullable(
DaoUtil.getData(entityViewRepository.findByTenantIdAndName(fromTimeUUID(tenantId), name)));
}
@Override
public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId,
UUID customerId,
TextPageLink pageLink) {
return DaoUtil.convertDataList(
entityViewRepository.findByTenantIdAndCustomerId(
fromTimeUUID(tenantId),
fromTimeUUID(customerId),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())
));
}
@Override
public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
return service.submit(() -> DaoUtil.convertDataList(
entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId))));
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java

@ -44,6 +44,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.dao.timeseries.TsInsertExecutorType;
import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
@ -60,7 +61,7 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
@Component
@Slf4j
@SqlDao
@SqlTsDao
public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao {
@Value("${sql.ts_inserts_executor_type}")

5
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -29,6 +29,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
@ -63,6 +64,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private DeviceService deviceService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private WidgetsBundleService widgetsBundleService;
@ -101,6 +105,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
customerService.deleteCustomersByTenantId(tenantId);
widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
dashboardService.deleteDashboardsByTenantId(tenantId);
entityViewService.deleteEntityViewsByTenantId(tenantId);
assetService.deleteAssetsByTenantId(tenantId);
deviceService.deleteDevicesByTenantId(tenantId);
userService.deleteTenantAdmins(tenantId);

44
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -21,15 +21,22 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -46,10 +53,21 @@ public class BaseTimeseriesService implements TimeseriesService {
@Autowired
private TimeseriesDao timeseriesDao;
@Autowired
private EntityViewService entityViewService;
@Override
public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) {
validate(entityId);
queries.forEach(BaseTimeseriesService::validate);
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
List<ReadTsKvQuery> filteredQueries =
queries.stream()
.filter(query -> entityView.getKeys().getTimeseries().isEmpty() || entityView.getKeys().getTimeseries().contains(query.getKey()))
.collect(Collectors.toList());
return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, filteredQueries));
}
return timeseriesDao.findAllAsync(entityId, queries);
}
@ -58,6 +76,19 @@ public class BaseTimeseriesService implements TimeseriesService {
validate(entityId);
List<ListenableFuture<TsKvEntry>> futures = Lists.newArrayListWithExpectedSize(keys.size());
keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key));
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
List<String> filteredKeys = new ArrayList<>(keys);
if (!entityView.getKeys().getTimeseries().isEmpty()) {
filteredKeys.retainAll(entityView.getKeys().getTimeseries());
}
List<ReadTsKvQuery> queries =
filteredKeys.stream()
.map(key -> new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), entityView.getEndTimeMs(), 1, "ASC"))
.collect(Collectors.toList());
return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
}
keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
return Futures.allAsList(futures);
}
@ -92,11 +123,24 @@ public class BaseTimeseriesService implements TimeseriesService {
}
private void saveAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) {
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Only read only");
}
futures.add(timeseriesDao.savePartition(entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl));
futures.add(timeseriesDao.saveLatest(entityId, tsKvEntry));
futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl));
}
private List<ReadTsKvQuery> updateQueriesForEntityView(EntityView entityView, List<ReadTsKvQuery> queries) {
return queries.stream().map(query -> {
long startTs = entityView.getStartTimeMs() == 0 ? query.getStartTs() : entityView.getStartTimeMs();
long endTs = entityView.getEndTimeMs() == 0 ? query.getEndTs() : entityView.getEndTimeMs();
return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query :
new BaseReadTsKvQuery(query.getKey(), startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
}).collect(Collectors.toList());
}
@Override
public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
validate(entityId);

3
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -49,6 +49,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.dao.util.NoSqlTsDao;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
@ -70,7 +71,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
*/
@Component
@Slf4j
@NoSqlDao
@NoSqlTsDao
public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implements TimeseriesDao {
private static final int MIN_AGGREGATION_STEP_MS = 1000;

2
dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java

@ -34,7 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@Component
@Slf4j
@NoSqlDao
@NoSqlAnyDao
public class BufferedRateLimiter implements AsyncRateLimiter {
private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

22
dao/src/main/java/org/thingsboard/server/dao/util/NoSqlAnyDao.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
@ConditionalOnExpression("'${database.ts.type}'=='cassandra' || '${database.entities.type}'=='cassandra'")
public @interface NoSqlAnyDao {
}

2
dao/src/main/java/org/thingsboard/server/dao/util/NoSqlDao.java

@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "cassandra")
@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "cassandra")
public @interface NoSqlDao {
}

22
dao/src/main/java/org/thingsboard/server/dao/util/NoSqlTsDao.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty(prefix = "database.ts", value = "type", havingValue = "cassandra")
public @interface NoSqlTsDao {
}

2
dao/src/main/java/org/thingsboard/server/dao/util/SqlDao.java

@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "sql")
@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql")
public @interface SqlDao {
}

22
dao/src/main/java/org/thingsboard/server/dao/util/SqlTsDao.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty(prefix = "database.ts", value = "type", havingValue = "sql")
public @interface SqlTsDao {
}

124
dao/src/main/resources/cassandra/schema.cql → dao/src/main/resources/cassandra/schema-entities.cql

@ -165,35 +165,55 @@ CREATE TABLE IF NOT EXISTS thingsboard.device (
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS
SELECT *
from thingsboard.device
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
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
PRIMARY KEY ( tenant_id, name, id, customer_id, type)
WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS
SELECT *
from thingsboard.device
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
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
PRIMARY KEY ( tenant_id, search_text, id, customer_id, type)
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS
SELECT *
from thingsboard.device
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
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
PRIMARY KEY ( tenant_id, type, search_text, id, customer_id)
WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS
SELECT *
from thingsboard.device
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
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
PRIMARY KEY ( customer_id, tenant_id, search_text, id, type )
WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS
SELECT *
from thingsboard.device
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
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
PRIMARY KEY ( customer_id, tenant_id, type, search_text, id )
WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC );
@ -378,41 +398,6 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_searc
PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
key text,
partition bigint,
ts bigint,
bool_v boolean,
str_v text,
long_v bigint,
dbl_v double,
PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)
);
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
key text,
partition bigint,
PRIMARY KEY (( entity_type, entity_id, key ), partition)
) WITH CLUSTERING ORDER BY ( partition ASC )
AND compaction = { 'class' : 'LeveledCompactionStrategy' };
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
key text,
ts bigint,
bool_v boolean,
str_v text,
long_v bigint,
dbl_v double,
PRIMARY KEY (( entity_type, entity_id ), key)
) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };
CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
@ -638,3 +623,62 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
additional_info text,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
id timeuuid,
entity_id timeuuid,
entity_type text,
tenant_id timeuuid,
customer_id timeuuid,
name text,
keys text,
start_ts bigint,
end_ts bigint,
search_text text,
additional_info text,
PRIMARY KEY (id, entity_id, tenant_id, customer_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND name IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);

55
dao/src/main/resources/cassandra/schema-ts.cql

@ -0,0 +1,55 @@
--
-- Copyright © 2016-2018 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE KEYSPACE IF NOT EXISTS thingsboard
WITH replication = {
'class' : 'SimpleStrategy',
'replication_factor' : 1
};
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
key text,
partition bigint,
ts bigint,
bool_v boolean,
str_v text,
long_v bigint,
dbl_v double,
PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)
);
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
key text,
partition bigint,
PRIMARY KEY (( entity_type, entity_id, key ), partition)
) WITH CLUSTERING ORDER BY ( partition ASC )
AND compaction = { 'class' : 'LeveledCompactionStrategy' };
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT)
entity_id timeuuid,
key text,
ts bigint,
bool_v boolean,
str_v text,
long_v bigint,
dbl_v double,
PRIMARY KEY (( entity_type, entity_id ), key)
) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };

38
dao/src/main/resources/sql/schema.sql → dao/src/main/resources/sql/schema-entities.sql

@ -179,30 +179,6 @@ CREATE TABLE IF NOT EXISTS tenant (
zip varchar(255)
);
CREATE TABLE IF NOT EXISTS ts_kv (
entity_type varchar(255) NOT NULL,
entity_id varchar(31) NOT NULL,
key varchar(255) NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts)
);
CREATE TABLE IF NOT EXISTS ts_kv_latest (
entity_type varchar(255) NOT NULL,
entity_id varchar(31) NOT NULL,
key varchar(255) NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key)
);
CREATE TABLE IF NOT EXISTS user_credentials (
id varchar(31) NOT NULL CONSTRAINT user_credentials_pkey PRIMARY KEY,
activate_token varchar(255) UNIQUE,
@ -251,3 +227,17 @@ CREATE TABLE IF NOT EXISTS rule_node (
debug_mode boolean,
search_text varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_views (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
name varchar(255),
keys varchar(255),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);

39
dao/src/main/resources/sql/schema-ts.sql

@ -0,0 +1,39 @@
--
-- Copyright © 2016-2018 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS ts_kv (
entity_type varchar(255) NOT NULL,
entity_id varchar(31) NOT NULL,
key varchar(255) NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts)
);
CREATE TABLE IF NOT EXISTS ts_kv_latest (
entity_type varchar(255) NOT NULL,
entity_id varchar(31) NOT NULL,
key varchar(255) NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key)
);

2
dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java

@ -30,7 +30,7 @@ public class JpaDaoTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql",
"sql-test.properties"
);

4
dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java

@ -34,7 +34,9 @@ public class NoSqlDaoServiceTestSuite {
@ClassRule
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)),
"cassandra-test.yaml", 30000L);

2
dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java

@ -30,7 +30,7 @@ public class SqlDaoServiceTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema.sql", "sql/system-data.sql", "sql/system-test.sql"),
Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql", "sql/system-test.sql"),
"sql/drop-all-tables.sql",
"sql-test.properties"
);

3
dao/src/test/resources/application-test.properties

@ -24,6 +24,9 @@ caffeine.specs.devices.maxSize=100000
caffeine.specs.assets.timeToLiveInMinutes=1440
caffeine.specs.assets.maxSize=100000
caffeine.specs.entityViews.timeToLiveInMinutes=1440
caffeine.specs.entityViews.maxSize=100000
caching.specs.devices.timeToLiveInMinutes=1440
caching.specs.devices.maxSize=100000

3
dao/src/test/resources/nosql-test.properties

@ -1,4 +1,5 @@
database.type=cassandra
database.entities.type=cassandra
database.ts.type=cassandra
cassandra.queue.partitioning=HOURS
cassandra.queue.ack.ttl=3600

3
dao/src/test/resources/sql-test.properties

@ -1,4 +1,5 @@
database.type=sql
database.ts.type=sql
database.entities.type=sql
sql.ts_inserts_executor_type=fixed
sql.ts_inserts_fixed_thread_pool_size=10

3
dao/src/test/resources/sql/drop-all-tables.sql

@ -18,4 +18,5 @@ DROP TABLE IF EXISTS user_credentials;
DROP TABLE IF EXISTS widget_type;
DROP TABLE IF EXISTS widgets_bundle;
DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS entity_views;

4
docker/k8s/cassandra-setup.yaml

@ -30,7 +30,9 @@ spec:
value: "cassandra-headless"
- name : CASSANDRA_PORT
value: "9042"
- name : DATABASE_TYPE
- name : DATABASE_ENTITIES_TYPE
value: "cassandra"
- name : DATABASE_TS_TYPE
value: "cassandra"
- name : CASSANDRA_URL
value: "cassandra-headless:9042"

4
docker/k8s/cassandra-upgrade.yaml

@ -30,7 +30,9 @@ spec:
value: "cassandra-headless"
- name : CASSANDRA_PORT
value: "9042"
- name : DATABASE_TYPE
- name : DATABASE_ENTITIES_TYPE
value: "cassandra"
- name : DATABASE_TS_TYPE
value: "cassandra"
- name : CASSANDRA_URL
value: "cassandra-headless:9042"

7
docker/k8s/tb.yaml

@ -120,7 +120,12 @@ spec:
configMapKeyRef:
name: tb-config
key: cassandra.url
- name: DATABASE_TYPE
- name: DATABASE_ENTITIES_TYPE
valueFrom:
configMapKeyRef:
name: tb-config
key: database.type
- name: DATABASE_TS_TYPE
valueFrom:
configMapKeyRef:
name: tb-config

3
docker/tb.env

@ -8,7 +8,8 @@ COAP_BIND_PORT=5683
ZOOKEEPER_URL=zk:2181
# type of database to use: sql[DEFAULT] or cassandra
DATABASE_TYPE=sql
DATABASE_TS_TYPE=sql
DATABASE_ENTITIES_TYPE=sql
# cassandra db config
CASSANDRA_URL=cassandra:9042

4
docker/tb/run-application.sh

@ -23,7 +23,7 @@ printenv | awk -F "=" '{print "export " $1 "='\''" $2 "'\''"}' >> /usr/share/thi
cat /usr/share/thingsboard/conf/thingsboard.conf
if [ "$DATABASE_TYPE" == "cassandra" ]; then
if [ "$DATABASE_ENTITIES_TYPE" == "cassandra" ]; then
until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open\|filtered"
do
echo "Wait for cassandra db to start..."
@ -31,7 +31,7 @@ if [ "$DATABASE_TYPE" == "cassandra" ]; then
done
fi
if [ "$DATABASE_TYPE" == "sql" ]; then
if [ "$DATABASE_ENTITIES_TYPE" == "sql" ]; then
if [ "$SPRING_DRIVER_CLASS_NAME" == "org.postgresql.Driver" ]; then
until nmap $POSTGRES_HOST -p $POSTGRES_PORT | grep "$POSTGRES_PORT/tcp open"
do

3
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -26,6 +26,7 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
@ -83,6 +84,8 @@ public interface TbContext {
RelationService getRelationService();
EntityViewService getEntityViewService();
ListeningExecutor getJsExecutor();
ListeningExecutor getMailExecutor();

137
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java

@ -0,0 +1,137 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.action;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.util.DonAsynchron;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "copy attributes",
configClazz = EmptyNodeConfiguration.class,
nodeDescription = "Copy attributes from asset/device to entity view and changes message originator to related entity view",
nodeDetails = "Copy attributes from asset/device to related entity view according to entity view configuration. \n " +
"Copy will be done only for attributes that are between start and end dates and according to attribute keys configuration. \n" +
"Changes message originator to related entity view and produces new messages according to count of updated entity views",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbNodeEmptyConfig",
icon = "content_copy"
)
public class TbCopyAttributesToEntityViewNode implements TbNode {
EmptyNodeConfiguration config;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
if (!msg.getMetaData().getData().isEmpty()) {
long now = System.currentTimeMillis();
String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ?
DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope");
ListenableFuture<List<EntityView>> entityViewsFuture =
ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator());
DonAsynchron.withCallback(entityViewsFuture,
entityViews -> {
for (EntityView entityView : entityViews) {
long startTime = entityView.getStartTimeMs();
long endTime = entityView.getEndTimeMs();
if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) {
Set<AttributeKvEntry> attributes =
JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes();
List<AttributeKvEntry> filteredAttributes =
attributes.stream()
.filter(attr -> {
switch (scope) {
case DataConstants.CLIENT_SCOPE:
if (entityView.getKeys().getAttributes().getCs().isEmpty()) {
return true;
}
return entityView.getKeys().getAttributes().getCs().contains(attr.getKey());
case DataConstants.SERVER_SCOPE:
if (entityView.getKeys().getAttributes().getSs().isEmpty()) {
return true;
}
return entityView.getKeys().getAttributes().getSs().contains(attr.getKey());
case DataConstants.SHARED_SCOPE:
if (entityView.getKeys().getAttributes().getSh().isEmpty()) {
return true;
}
return entityView.getKeys().getAttributes().getSh().contains(attr.getKey());
}
return false;
}).collect(Collectors.toList());
ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes,
new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData());
ctx.tellNext(updMsg, SUCCESS);
}
@Override
public void onFailure(Throwable t) {
ctx.tellFailure(msg, t);
}
});
}
}
},
t -> ctx.tellFailure(msg, t));
} else {
ctx.tellNext(msg, FAILURE);
}
}
@Override
public void destroy() {
}
}

238
ui/src/app/api/entity-view.service.js

@ -0,0 +1,238 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import thingsboardTypes from '../common/types.constant';
export default angular.module('thingsboard.api.entityView', [thingsboardTypes])
.factory('entityViewService', EntityViewService)
.name;
/*@ngInject*/
function EntityViewService($http, $q, $window, userService, attributeService, customerService, types) {
var service = {
assignEntityViewToCustomer: assignEntityViewToCustomer,
deleteEntityView: deleteEntityView,
getCustomerEntityViews: getCustomerEntityViews,
getEntityView: getEntityView,
getEntityViews: getEntityViews,
getTenantEntityViews: getTenantEntityViews,
saveEntityView: saveEntityView,
unassignEntityViewFromCustomer: unassignEntityViewFromCustomer,
getEntityViewAttributes: getEntityViewAttributes,
subscribeForEntityViewAttributes: subscribeForEntityViewAttributes,
unsubscribeForEntityViewAttributes: unsubscribeForEntityViewAttributes,
findByQuery: findByQuery,
getEntityViewTypes: getEntityViewTypes
}
return service;
function getTenantEntityViews(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/entityViews?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
if (angular.isDefined(type) && type.length) {
url += '&type=' + type;
}
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
function success(data) {
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
}
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getCustomerEntityViews(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/entityViews?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
if (angular.isDefined(type) && type.length) {
url += '&type=' + type;
}
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
function success(data) {
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
}
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getEntityView(entityViewId, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/entityView/' + entityViewId;
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function getEntityViews(entityViewIds, config) {
var deferred = $q.defer();
var ids = '';
for (var i=0;i<entityViewIds.length;i++) {
if (i>0) {
ids += ',';
}
ids += entityViewIds[i];
}
var url = '/api/entityViews?entityViewIds=' + ids;
$http.get(url, config).then(function success(response) {
var entityViews = response.data;
entityViews.sort(function (entityView1, entityView2) {
var id1 = entityView1.id.id;
var id2 = entityView2.id.id;
var index1 = entityViewIds.indexOf(id1);
var index2 = entityViewIds.indexOf(id2);
return index1 - index2;
});
deferred.resolve(entityViews);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function saveEntityView(entityView) {
var deferred = $q.defer();
var url = '/api/entityView';
$http.post(url, entityView).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function deleteEntityView(entityViewId) {
var deferred = $q.defer();
var url = '/api/entityView/' + entityViewId;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function assignEntityViewToCustomer(customerId, entityViewId) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/entityView/' + entityViewId;
$http.post(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function unassignEntityViewFromCustomer(entityViewId) {
var deferred = $q.defer();
var url = '/api/customer/entityView/' + entityViewId;
$http.delete(url).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getEntityViewAttributes(entityViewId, attributeScope, query, successCallback, config) {
return attributeService.getEntityAttributes(types.entityType.entityView, entityViewId, attributeScope, query, successCallback, config);
}
function subscribeForEntityViewAttributes(entityViewId, attributeScope) {
return attributeService.subscribeForEntityAttributes(types.entityType.entityView, entityViewId, attributeScope);
}
function unsubscribeForEntityViewAttributes(subscriptionId) {
attributeService.unsubscribeForEntityAttributes(subscriptionId);
}
function findByQuery(query, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/entityViews';
if (!config) {
config = {};
}
config = Object.assign(config, { ignoreErrors: ignoreErrors });
$http.post(url, query, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getEntityViewTypes(config) {
var deferred = $q.defer();
var url = '/api/entityView/types';
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

17
ui/src/app/api/entity.service.js

@ -20,8 +20,9 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
.name;
/*@ngInject*/
function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
assetService, tenantService, customerService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) {
function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, assetService, tenantService,
customerService, ruleChainService, dashboardService, entityRelationService, attributeService,
entityViewService, types, utils) {
var service = {
getEntity: getEntity,
getEntities: getEntities,
@ -54,6 +55,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
case types.entityType.asset:
promise = assetService.getAsset(entityId, true, config);
break;
case types.entityType.entityView:
promise = entityViewService.getEntityView(entityId, true, config);
break;
case types.entityType.tenant:
promise = tenantService.getTenant(entityId, config);
break;
@ -239,6 +243,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
promise = assetService.getTenantAssets(pageLink, false, config, subType);
}
break;
case types.entityType.entityView:
if (user.authority === 'CUSTOMER_USER') {
promise = entityViewService.getCustomerEntityViews(customerId, pageLink, false, config, subType);
} else {
promise = entityViewService.getTenantEntityViews(pageLink, false, config, subType);
}
break;
case types.entityType.tenant:
if (user.authority === 'TENANT_ADMIN') {
promise = getSingleTenantByPageLinkPromise(pageLink, config);
@ -725,6 +736,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
case 'TENANT_ADMIN':
entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset;
entityTypes.entityView = types.entityType.entityView;
entityTypes.tenant = types.entityType.tenant;
entityTypes.customer = types.entityType.customer;
entityTypes.dashboard = types.entityType.dashboard;
@ -735,6 +747,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
case 'CUSTOMER_USER':
entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset;
entityTypes.entityView = types.entityType.entityView;
entityTypes.customer = types.entityType.customer;
entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) {

2
ui/src/app/app.js

@ -67,6 +67,7 @@ import thingsboardClipboard from './services/clipboard.service';
import thingsboardHome from './layout';
import thingsboardApiLogin from './api/login.service';
import thingsboardApiDevice from './api/device.service';
import thingsboardApiEntityView from './api/entity-view.service';
import thingsboardApiUser from './api/user.service';
import thingsboardApiEntityRelation from './api/entity-relation.service';
import thingsboardApiAsset from './api/asset.service';
@ -133,6 +134,7 @@ angular.module('thingsboard', [
thingsboardHome,
thingsboardApiLogin,
thingsboardApiDevice,
thingsboardApiEntityView,
thingsboardApiUser,
thingsboardApiEntityRelation,
thingsboardApiAsset,

9
ui/src/app/common/types.constant.js

@ -327,7 +327,8 @@ export default angular.module('thingsboard.types', [])
dashboard: "DASHBOARD",
alarm: "ALARM",
rulechain: "RULE_CHAIN",
rulenode: "RULE_NODE"
rulenode: "RULE_NODE",
entityView: "ENTITY_VIEW"
},
aliasEntityType: {
current_customer: "CURRENT_CUSTOMER"
@ -345,6 +346,12 @@ export default angular.module('thingsboard.types', [])
list: 'entity.list-of-assets',
nameStartsWith: 'entity.asset-name-starts-with'
},
"ENTITY_VIEW": {
type: 'entity.type-entity-view',
typePlural: 'entity.type-entity-views',
list: 'entity.list-of-entity-views',
nameStartsWith: 'entity.entity-view-name-starts-with'
},
"TENANT": {
type: 'entity.type-tenant',
typePlural: 'entity.type-tenants',

45
ui/src/app/entity-view/add-entity-view.tpl.html

@ -0,0 +1,45 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'entity-view.add' | translate }}" tb-help="'entityViews'" help-container-id="help-container">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>entity-view.add</h2>
<span flex></span>
<div id="help-container"></div>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-entity-view entity-view="vm.item" is-edit="true" the-form="theForm"></tb-entity-view>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
{{ 'action.add' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>

123
ui/src/app/entity-view/add-entity-views-to-customer.controller.js

@ -0,0 +1,123 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function AddEntityViewsToCustomerController(entityViewService, $mdDialog, $q, customerId, entityViews) {
var vm = this;
vm.entityViews = entityViews;
vm.searchText = '';
vm.assign = assign;
vm.cancel = cancel;
vm.hasData = hasData;
vm.noData = noData;
vm.searchEntityViewTextUpdated = searchEntityViewTextUpdated;
vm.toggleEntityViewSelection = toggleEntityViewSelection;
vm.theEntityViews = {
getItemAtIndex: function (index) {
if (index > vm.entityViews.data.length) {
vm.theEntityViews.fetchMoreItems_(index);
return null;
}
var item = vm.entityViews.data[index];
if (item) {
item.indexNumber = index + 1;
}
return item;
},
getLength: function () {
if (vm.entityViews.hasNext) {
return vm.entityViews.data.length + vm.entityViews.nextPageLink.limit;
} else {
return vm.entityViews.data.length;
}
},
fetchMoreItems_: function () {
if (vm.entityViews.hasNext && !vm.entityViews.pending) {
vm.entityViews.pending = true;
entityViewService.getTenantEntityViews(vm.entityViews.nextPageLink, false).then(
function success(entityViews) {
vm.entityViews.data = vm.entityViews.data.concat(entityViews.data);
vm.entityViews.nextPageLink = entityViews.nextPageLink;
vm.entityViews.hasNext = entityViews.hasNext;
if (vm.entityViews.hasNext) {
vm.entityViews.nextPageLink.limit = vm.entityViews.pageSize;
}
vm.entityViews.pending = false;
},
function fail() {
vm.entityViews.hasNext = false;
vm.entityViews.pending = false;
});
}
}
};
function cancel () {
$mdDialog.cancel();
}
function assign() {
var tasks = [];
for (var entityViewId in vm.entityViews.selections) {
tasks.push(entityViewService.assignEntityViewToCustomer(customerId, entityViewId));
}
$q.all(tasks).then(function () {
$mdDialog.hide();
});
}
function noData() {
return vm.entityViews.data.length == 0 && !vm.entityViews.hasNext;
}
function hasData() {
return vm.entityViews.data.length > 0;
}
function toggleEntityViewSelection($event, entityView) {
$event.stopPropagation();
var selected = angular.isDefined(entityView.selected) && entityView.selected;
entityView.selected = !selected;
if (entityView.selected) {
vm.entityViews.selections[entityView.id.id] = true;
vm.entityViews.selectedCount++;
} else {
delete vm.entityViews.selections[entityView.id.id];
vm.entityViews.selectedCount--;
}
}
function searchEntityViewTextUpdated() {
vm.entityViews = {
pageSize: vm.entityViews.pageSize,
data: [],
nextPageLink: {
limit: vm.entityViews.pageSize,
textSearch: vm.searchText
},
selections: {},
selectedCount: 0,
hasNext: true,
pending: false
};
}
}

77
ui/src/app/entity-view/add-entity-views-to-customer.tpl.html

@ -0,0 +1,77 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'entity-view.assign-to-customer' | translate }}">
<form name="theForm" ng-submit="vm.assign()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>entity-view.assign-entity-view-to-customer</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset>
<span translate>entity-view.assign-entity-view-to-customer-text</span>
<md-input-container class="md-block" style='margin-bottom: 0px;'>
<label>&nbsp;</label>
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
search
</md-icon>
<input id="entity-view-search" autofocus ng-model="vm.searchText"
ng-change="vm.searchEntityViewTextUpdated()"
placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<div style='min-height: 150px;'>
<span translate layout-align="center center"
style="text-transform: uppercase; display: flex; height: 150px;"
class="md-subhead"
ng-show="vm.noData()">entity-view.no-entity-views-text</span>
<md-virtual-repeat-container ng-show="vm.hasData()"
tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
style='min-height: 150px; width: 100%;'>
<md-list>
<md-list-item md-virtual-repeat="entityView in vm.theEntityViews" md-on-demand
class="repeated-item" flex>
<md-checkbox ng-click="vm.toggleEntityViewSelection($event, entityView)"
aria-label="{{ 'item.selected' | translate }}"
ng-checked="entityView.selected"></md-checkbox>
<span> {{ entityView.name }} </span>
</md-list-item>
</md-list>
</md-virtual-repeat-container>
</div>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || vm.entityViews.selectedCount == 0" type="submit"
class="md-raised md-primary">
{{ 'action.assign' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

123
ui/src/app/entity-view/assign-to-customer.controller.js

@ -0,0 +1,123 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function AssignEntityViewToCustomerController(customerService, entityViewService, $mdDialog, $q, entityViewIds, customers) {
var vm = this;
vm.customers = customers;
vm.searchText = '';
vm.assign = assign;
vm.cancel = cancel;
vm.isCustomerSelected = isCustomerSelected;
vm.hasData = hasData;
vm.noData = noData;
vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
vm.toggleCustomerSelection = toggleCustomerSelection;
vm.theCustomers = {
getItemAtIndex: function (index) {
if (index > vm.customers.data.length) {
vm.theCustomers.fetchMoreItems_(index);
return null;
}
var item = vm.customers.data[index];
if (item) {
item.indexNumber = index + 1;
}
return item;
},
getLength: function () {
if (vm.customers.hasNext) {
return vm.customers.data.length + vm.customers.nextPageLink.limit;
} else {
return vm.customers.data.length;
}
},
fetchMoreItems_: function () {
if (vm.customers.hasNext && !vm.customers.pending) {
vm.customers.pending = true;
customerService.getCustomers(vm.customers.nextPageLink).then(
function success(customers) {
vm.customers.data = vm.customers.data.concat(customers.data);
vm.customers.nextPageLink = customers.nextPageLink;
vm.customers.hasNext = customers.hasNext;
if (vm.customers.hasNext) {
vm.customers.nextPageLink.limit = vm.customers.pageSize;
}
vm.customers.pending = false;
},
function fail() {
vm.customers.hasNext = false;
vm.customers.pending = false;
});
}
}
};
function cancel() {
$mdDialog.cancel();
}
function assign() {
var tasks = [];
for (var i=0; i < entityViewIds.length;i++) {
tasks.push(entityViewService.assignEntityViewToCustomer(vm.customers.selection.id.id, entityViewIds[i]));
}
$q.all(tasks).then(function () {
$mdDialog.hide();
});
}
function noData() {
return vm.customers.data.length == 0 && !vm.customers.hasNext;
}
function hasData() {
return vm.customers.data.length > 0;
}
function toggleCustomerSelection($event, customer) {
$event.stopPropagation();
if (vm.isCustomerSelected(customer)) {
vm.customers.selection = null;
} else {
vm.customers.selection = customer;
}
}
function isCustomerSelected(customer) {
return vm.customers.selection != null && customer &&
customer.id.id === vm.customers.selection.id.id;
}
function searchCustomerTextUpdated() {
vm.customers = {
pageSize: vm.customers.pageSize,
data: [],
nextPageLink: {
limit: vm.customers.pageSize,
textSearch: vm.searchText
},
selection: null,
hasNext: true,
pending: false
};
}
}

76
ui/src/app/entity-view/assign-to-customer.tpl.html

@ -0,0 +1,76 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'entity-view.assign-entity-view-to-customer' | translate }}">
<form name="theForm" ng-submit="vm.assign()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>entity-view.assign-entity-view-to-customer</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset>
<span translate>entity-view.assign-to-customer-text</span>
<md-input-container class="md-block" style='margin-bottom: 0px;'>
<label>&nbsp;</label>
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
search
</md-icon>
<input id="customer-search" autofocus ng-model="vm.searchText"
ng-change="vm.searchCustomerTextUpdated()"
placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<div style='min-height: 150px;'>
<span translate layout-align="center center"
style="text-transform: uppercase; display: flex; height: 150px;"
class="md-subhead"
ng-show="vm.noData()">customer.no-customers-text</span>
<md-virtual-repeat-container ng-show="vm.hasData()"
tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
style='min-height: 150px; width: 100%;'>
<md-list>
<md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
class="repeated-item" flex>
<md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
aria-label="{{ 'item.selected' | translate }}"
ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
<span> {{ customer.title }} </span>
</md-list-item>
</md-list>
</md-virtual-repeat-container>
</div>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
{{ 'action.assign' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

22
ui/src/app/entity-view/entity-view-card.tpl.html

@ -0,0 +1,22 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div flex layout="column" style="margin-top: -10px;">
<div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
<div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
<div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'entity-view.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
</div>

119
ui/src/app/entity-view/entity-view-fieldset.tpl.html

@ -0,0 +1,119 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-button ng-click="onMakePublic({event: $event})"
ng-show="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer && !isPublic"
class="md-raised md-primary">{{ 'entity-view.make-public' | translate }}</md-button>
<md-button ng-click="onAssignToCustomer({event: $event})"
ng-show="!isEdit && entityViewScope === 'tenant' && !isAssignedToCustomer"
class="md-raised md-primary">{{ 'entity-view.assign-to-customer' | translate }}</md-button>
<md-button ng-click="onUnassignFromCustomer({event: $event})"
ng-show="!isEdit && (entityViewScope === 'customer' || entityViewScope === 'tenant') && isAssignedToCustomer"
class="md-raised md-primary">{{'entity-view.unassign-from-customer' | translate }}</md-button>
<md-button ng-click="onDeleteEntityView({event: $event})"
ng-show="!isEdit && entityViewScope === 'tenant'"
class="md-raised md-primary">{{ 'entity-view.delete' | translate }}</md-button>
<div layout="row">
<md-button ngclipboard data-clipboard-action="copy"
ngclipboard-success="onEntityViewIdCopied(e)"
data-clipboard-text="{{entityView.id.id}}" ng-show="!isEdit"
class="md-raised">
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>entity-view.copyId</span>
</md-button>
</div>
<md-content class="md-padding" layout="column">
<md-input-container class="md-block"
ng-show="!isEdit && isAssignedToCustomer && entityViewScope === 'tenant'">
<label translate>entity-view.assignedToCustomer</label>
<input ng-model="assignedCustomer.title" disabled>
</md-input-container>
<fieldset ng-disabled="$root.loading || !isEdit">
<md-input-container class="md-block">
<label translate>entity-view.name</label>
<input required name="name" ng-model="entityView.name">
<div ng-messages="theForm.name.$error">
<div translate ng-message="required">entity-view.name-required</div>
</div>
</md-input-container>
<tb-entity-select flex ng-disabled="!isEdit"
the-form="theForm"
tb-required="true"
allowed-entity-types="allowedEntityTypes"
ng-model="entityView.entityId">
</tb-entity-select>
<md-input-container class="md-block">
<label translate>entity-view.description</label>
<textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>
</md-input-container>
<section layout="column">
<label translate class="tb-title no-padding">entity-view.client-attributes</label>
<md-chips style="padding-bottom: 15px;"
ng-required="false"
readonly="!isEdit"
ng-model="entityView.keys.attributes.cs"
placeholder="{{'entity-view.client-attributes' | translate}}"
md-separator-keys="separatorKeys">
</md-chips>
<label translate class="tb-title no-padding">entity-view.shared-attributes</label>
<md-chips style="padding-bottom: 15px;"
ng-required="false"
readonly="!isEdit"
ng-model="entityView.keys.attributes.sh"
placeholder="{{'entity-view.shared-attributes' | translate}}"
md-separator-keys="separatorKeys">
</md-chips>
<label translate class="tb-title no-padding">entity-view.server-attributes</label>
<md-chips style="padding-bottom: 15px;"
ng-required="false"
readonly="!isEdit"
ng-model="entityView.keys.attributes.ss"
placeholder="{{'entity-view.server-attributes' | translate}}"
md-separator-keys="separatorKeys">
</md-chips>
<label translate class="tb-title no-padding">entity-view.latest-timeseries</label>
<md-chips ng-required="false"
readonly="!isEdit"
ng-model="entityView.keys.timeseries"
placeholder="{{'entity-view.latest-timeseries' | translate}}"
md-separator-keys="separatorKeys">
</md-chips>
</section>
<section layout="column">
<section layout="row" layout-align="start start">
<mdp-date-picker ng-model="startTimeMs"
mdp-max-date="maxStartTimeMs"
mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"></mdp-date-picker>
<mdp-time-picker ng-model="startTimeMs"
mdp-max-date="maxStartTimeMs"
mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"
mdp-auto-switch="true"></mdp-time-picker>
</section>
<section layout="row" layout-align="start start">
<mdp-date-picker ng-model="endTimeMs"
mdp-min-date="minEndTimeMs"
mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"></mdp-date-picker>
<mdp-time-picker ng-model="endTimeMs"
mdp-min-date="minEndTimeMs"
mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"
mdp-auto-switch="true"></mdp-time-picker>
</section>
</section>
</fieldset>
</md-content>

483
ui/src/app/entity-view/entity-view.controller.js

@ -0,0 +1,483 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import addEntityViewTemplate from './add-entity-view.tpl.html';
import entityViewCard from './entity-view-card.tpl.html';
import assignToCustomerTemplate from './assign-to-customer.tpl.html';
import addEntityViewsToCustomerTemplate from './add-entity-views-to-customer.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export function EntityViewCardController(types) {
var vm = this;
vm.types = types;
vm.isAssignedToCustomer = function() {
if (vm.item && vm.item.customerId && vm.parentCtl.entityViewsScope === 'tenant' &&
vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) {
return true;
}
return false;
}
vm.isPublic = function() {
if (vm.item && vm.item.assignedCustomer && vm.parentCtl.entityViewsScope === 'tenant' && vm.item.assignedCustomer.isPublic) {
return true;
}
return false;
}
}
/*@ngInject*/
export function EntityViewController($rootScope, userService, entityViewService, customerService, $state, $stateParams,
$document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
var entityViewActionsList = [];
var entityViewGroupActionsList = [];
var vm = this;
vm.types = types;
vm.entityViewGridConfig = {
deleteItemTitleFunc: deleteEntityViewTitle,
deleteItemContentFunc: deleteEntityViewText,
deleteItemsTitleFunc: deleteEntityViewsTitle,
deleteItemsActionTitleFunc: deleteEntityViewsActionTitle,
deleteItemsContentFunc: deleteEntityViewsText,
saveItemFunc: saveEntityView,
getItemTitleFunc: getEntityViewTitle,
itemCardController: 'EntityViewCardController',
itemCardTemplateUrl: entityViewCard,
parentCtl: vm,
actionsList: entityViewActionsList,
groupActionsList: entityViewGroupActionsList,
onGridInited: gridInited,
addItemTemplateUrl: addEntityViewTemplate,
addItemText: function() { return $translate.instant('entity-view.add-entity-view-text') },
noItemsText: function() { return $translate.instant('entity-view.no-entity-views-text') },
itemDetailsText: function() { return $translate.instant('entity-view.entity-view-details') },
isDetailsReadOnly: isCustomerUser,
isSelectionEnabled: function () {
return !isCustomerUser();
}
};
if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
vm.entityViewGridConfig.items = $stateParams.items;
}
if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
vm.entityViewGridConfig.topIndex = $stateParams.topIndex;
}
vm.entityViewsScope = $state.$current.data.entityViewsType;
vm.assignToCustomer = assignToCustomer;
vm.makePublic = makePublic;
vm.unassignFromCustomer = unassignFromCustomer;
initController();
function initController() {
var fetchEntityViewsFunction = null;
var deleteEntityViewFunction = null;
var refreshEntityViewsParamsFunction = null;
var user = userService.getCurrentUser();
if (user.authority === 'CUSTOMER_USER') {
vm.entityViewsScope = 'customer_user';
customerId = user.customerId;
}
if (customerId) {
vm.customerEntityViewsTitle = $translate.instant('customer.entity-views');
customerService.getShortCustomerInfo(customerId).then(
function success(info) {
if (info.isPublic) {
vm.customerEntityViewsTitle = $translate.instant('customer.public-entity-views');
}
}
);
}
if (vm.entityViewsScope === 'tenant') {
fetchEntityViewsFunction = function (pageLink, entityViewType) {
return entityViewService.getTenantEntityViews(pageLink, true, null, entityViewType);
};
deleteEntityViewFunction = function (entityViewId) {
return entityViewService.deleteEntityView(entityViewId);
};
refreshEntityViewsParamsFunction = function() {
return {"topIndex": vm.topIndex};
};
entityViewActionsList.push(
{
onAction: function ($event, item) {
assignToCustomer($event, [ item.id.id ]);
},
name: function() { return $translate.instant('action.assign') },
details: function() { return $translate.instant('entity-view.assign-to-customer') },
icon: "assignment_ind",
isEnabled: function(entityView) {
return entityView && (!entityView.customerId || entityView.customerId.id === types.id.nullUid);
}
}
);
entityViewActionsList.push(
{
onAction: function ($event, item) {
unassignFromCustomer($event, item, false);
},
name: function() { return $translate.instant('action.unassign') },
details: function() { return $translate.instant('entity-view.unassign-from-customer') },
icon: "assignment_return",
isEnabled: function(entityView) {
return entityView && entityView.customerId && entityView.customerId.id !== types.id.nullUid && !entityView.assignedCustomer.isPublic;
}
}
);
entityViewActionsList.push({
onAction: function ($event, item) {
unassignFromCustomer($event, item, true);
},
name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('entity-view.make-private') },
icon: "reply",
isEnabled: function(entityView) {
return entityView && entityView.customerId && entityView.customerId.id !== types.id.nullUid && entityView.assignedCustomer.isPublic;
}
});
entityViewActionsList.push(
{
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
},
name: function() { return $translate.instant('action.delete') },
details: function() { return $translate.instant('entity-view.delete') },
icon: "delete"
}
);
entityViewGroupActionsList.push(
{
onAction: function ($event, items) {
assignEntityViewsToCustomer($event, items);
},
name: function() { return $translate.instant('entity-view.assign-entity-views') },
details: function(selectedCount) {
return $translate.instant('entity-view.assign-entity-views-text', {count: selectedCount}, "messageformat");
},
icon: "assignment_ind"
}
);
entityViewGroupActionsList.push(
{
onAction: function ($event) {
vm.grid.deleteItems($event);
},
name: function() { return $translate.instant('entity-view.delete-entity-views') },
details: deleteEntityViewsActionTitle,
icon: "delete"
}
);
} else if (vm.entityViewsScope === 'customer' || vm.entityViewsScope === 'customer_user') {
fetchEntityViewsFunction = function (pageLink, entityViewType) {
return entityViewService.getCustomerEntityViews(customerId, pageLink, true, null, entityViewType);
};
deleteEntityViewFunction = function (entityViewId) {
return entityViewService.unassignEntityViewFromCustomer(entityViewId);
};
refreshEntityViewsParamsFunction = function () {
return {"customerId": customerId, "topIndex": vm.topIndex};
};
if (vm.entityViewsScope === 'customer') {
entityViewActionsList.push(
{
onAction: function ($event, item) {
unassignFromCustomer($event, item, false);
},
name: function() { return $translate.instant('action.unassign') },
details: function() { return $translate.instant('entity-view.unassign-from-customer') },
icon: "assignment_return",
isEnabled: function(entityView) {
return entityView && !entityView.assignedCustomer.isPublic;
}
}
);
entityViewGroupActionsList.push(
{
onAction: function ($event, items) {
unassignEntityViewsFromCustomer($event, items);
},
name: function() { return $translate.instant('entity-view.unassign-entity-views') },
details: function(selectedCount) {
return $translate.instant('entity-view.unassign-entity-views-action-title', {count: selectedCount}, "messageformat");
},
icon: "assignment_return"
}
);
vm.entityViewGridConfig.addItemAction = {
onAction: function ($event) {
addEntityViewsToCustomer($event);
},
name: function() { return $translate.instant('entity-view.assign-entity-views') },
details: function() { return $translate.instant('entity-view.assign-new-entity-view') },
icon: "add"
};
} else if (vm.entityViewsScope === 'customer_user') {
vm.entityViewGridConfig.addItemAction = {};
}
}
vm.entityViewGridConfig.refreshParamsFunc = refreshEntityViewsParamsFunction;
vm.entityViewGridConfig.fetchItemsFunc = fetchEntityViewsFunction;
vm.entityViewGridConfig.deleteItemFunc = deleteEntityViewFunction;
}
function deleteEntityViewTitle(entityView) {
return $translate.instant('entity-view.delete-entity-view-title', {entityViewName: entityView.name});
}
function deleteEntityViewText() {
return $translate.instant('entity-view.delete-entity-view-text');
}
function deleteEntityViewsTitle(selectedCount) {
return $translate.instant('entity-view.delete-entity-views-title', {count: selectedCount}, 'messageformat');
}
function deleteEntityViewsActionTitle(selectedCount) {
return $translate.instant('entity-view.delete-entity-views-action-title', {count: selectedCount}, 'messageformat');
}
function deleteEntityViewsText () {
return $translate.instant('entity-view.delete-entity-views-text');
}
function gridInited(grid) {
vm.grid = grid;
}
function getEntityViewTitle(entityView) {
return entityView ? entityView.name : '';
}
function saveEntityView(entityView) {
var deferred = $q.defer();
entityViewService.saveEntityView(entityView).then(
function success(savedEntityView) {
$rootScope.$broadcast('entityViewSaved');
var entityViews = [ savedEntityView ];
customerService.applyAssignedCustomersInfo(entityViews).then(
function success(items) {
if (items && items.length == 1) {
deferred.resolve(items[0]);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function isCustomerUser() {
return vm.entityViewsScope === 'customer_user';
}
function assignToCustomer($event, entityViewIds) {
if ($event) {
$event.stopPropagation();
}
var pageSize = 10;
customerService.getCustomers({limit: pageSize, textSearch: ''}).then(
function success(_customers) {
var customers = {
pageSize: pageSize,
data: _customers.data,
nextPageLink: _customers.nextPageLink,
selection: null,
hasNext: _customers.hasNext,
pending: false
};
if (customers.hasNext) {
customers.nextPageLink.limit = pageSize;
}
$mdDialog.show({
controller: 'AssignEntityViewToCustomerController',
controllerAs: 'vm',
templateUrl: assignToCustomerTemplate,
locals: {entityViewIds: entityViewIds, customers: customers},
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: $event
}).then(function () {
vm.grid.refreshList();
}, function () {
});
},
function fail() {
});
}
function addEntityViewsToCustomer($event) {
if ($event) {
$event.stopPropagation();
}
var pageSize = 10;
entityViewService.getTenantEntityViews({limit: pageSize, textSearch: ''}, false).then(
function success(_entityViews) {
var entityViews = {
pageSize: pageSize,
data: _entityViews.data,
nextPageLink: _entityViews.nextPageLink,
selections: {},
selectedCount: 0,
hasNext: _entityViews.hasNext,
pending: false
};
if (entityViews.hasNext) {
entityViews.nextPageLink.limit = pageSize;
}
$mdDialog.show({
controller: 'AddEntityViewsToCustomerController',
controllerAs: 'vm',
templateUrl: addEntityViewsToCustomerTemplate,
locals: {customerId: customerId, entityViews: entityViews},
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: $event
}).then(function () {
vm.grid.refreshList();
}, function () {
});
},
function fail() {
});
}
function assignEntityViewsToCustomer($event, items) {
var entityViewIds = [];
for (var id in items.selections) {
entityViewIds.push(id);
}
assignToCustomer($event, entityViewIds);
}
function unassignFromCustomer($event, entityView, isPublic) {
if ($event) {
$event.stopPropagation();
}
var title;
var content;
var label;
if (isPublic) {
title = $translate.instant('entity-view.make-private-entity-view-title', {entityViewName: entityView.name});
content = $translate.instant('entity-view.make-private-entity-view-text');
label = $translate.instant('entity-view.make-private');
} else {
title = $translate.instant('entity-view.unassign-entity-view-title', {entityViewName: entityView.name});
content = $translate.instant('entity-view.unassign-entity-view-text');
label = $translate.instant('entity-view.unassign-entity-view');
}
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title(title)
.htmlContent(content)
.ariaLabel(label)
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
entityViewService.unassignEntityViewFromCustomer(entityView.id.id).then(function success() {
vm.grid.refreshList();
});
});
}
function unassignEntityViewsFromCustomer($event, items) {
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title($translate.instant('entity-view.unassign-entity-views-title', {count: items.selectedCount}, 'messageformat'))
.htmlContent($translate.instant('entity-view.unassign-entity-views-text'))
.ariaLabel($translate.instant('entity-view.unassign-entity-view'))
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
var tasks = [];
for (var id in items.selections) {
tasks.push(entityViewService.unassignEntityViewFromCustomer(id));
}
$q.all(tasks).then(function () {
vm.grid.refreshList();
});
});
}
function makePublic($event, entityView) {
if ($event) {
$event.stopPropagation();
}
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title($translate.instant('entity-view.make-public-entity-view-title', {entityViewName: entityView.name}))
.htmlContent($translate.instant('entity-view.make-public-entity-view-text'))
.ariaLabel($translate.instant('entity-view.make-public'))
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
entityViewService.makeEntityViewPublic(entityView.id.id).then(function success() {
vm.grid.refreshList();
});
});
}
}

121
ui/src/app/entity-view/entity-view.directive.js

@ -0,0 +1,121 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntityViewDirective($compile, $templateCache, $filter, toast, $translate, $mdConstant,
types, clipboardService, entityViewService, customerService) {
var linker = function (scope, element) {
var template = $templateCache.get(entityViewFieldsetTemplate);
element.html(template);
scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
scope.allowedEntityTypes = [types.entityType.device, types.entityType.asset];
var semicolon = 186;
scope.separatorKeys = [$mdConstant.KEY_CODE.ENTER, $mdConstant.KEY_CODE.COMMA, semicolon];
scope.$watch('entityView', function(newVal) {
if (newVal) {
if (scope.entityView.customerId && scope.entityView.customerId.id !== types.id.nullUid) {
scope.isAssignedToCustomer = true;
customerService.getShortCustomerInfo(scope.entityView.customerId.id).then(
function success(customer) {
scope.assignedCustomer = customer;
scope.isPublic = customer.isPublic;
}
);
} else {
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
}
if (scope.entityView.startTimeMs > 0) {
scope.startTimeMs = new Date(scope.entityView.startTimeMs);
}
if (scope.entityView.endTimeMs > 0) {
scope.endTimeMs = new Date(scope.entityView.endTimeMs);
}
if (!scope.entityView.keys) {
scope.entityView.keys = {};
scope.entityView.keys.timeseries = [];
scope.entityView.keys.attributes = {};
scope.entityView.keys.attributes.ss = [];
scope.entityView.keys.attributes.cs = [];
scope.entityView.keys.attributes.sh = [];
}
}
});
scope.$watch('startTimeMs', function (newDate) {
if (newDate) {
if (newDate.getTime() > scope.maxStartTimeMs) {
scope.startTimeMs = angular.copy(scope.maxStartTimeMs);
}
updateMinMaxDates();
}
});
scope.$watch('endTimeMs', function (newDate) {
if (newDate) {
if (newDate.getTime() < scope.minEndTimeMs) {
scope.endTimeMs = angular.copy(scope.minEndTimeMs);
}
updateMinMaxDates();
}
});
function updateMinMaxDates() {
if (scope.endTimeMs) {
scope.maxStartTimeMs = angular.copy(new Date(scope.endTimeMs.getTime()));
scope.entityView.endTimeMs = scope.endTimeMs.getTime();
}
if (scope.startTimeMs) {
scope.minEndTimeMs = angular.copy(new Date(scope.startTimeMs.getTime()));
scope.entityView.startTimeMs = scope.startTimeMs.getTime();
}
}
scope.onEntityViewIdCopied = function() {
toast.showSuccess($translate.instant('entity-view.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
$compile(element.contents())(scope);
}
return {
restrict: "E",
link: linker,
scope: {
entityView: '=',
isEdit: '=',
entityViewScope: '=',
theForm: '=',
onAssignToCustomer: '&',
onMakePublic: '&',
onUnassignFromCustomer: '&',
onDeleteEntityView: '&'
}
};
}

72
ui/src/app/entity-view/entity-view.routes.js

@ -0,0 +1,72 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import entityViewsTemplate from './entity-views.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntityViewRoutes($stateProvider, types) {
$stateProvider
.state('home.entityViews', {
url: '/entityViews',
params: {'topIndex': 0},
module: 'private',
auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
views: {
"content@home": {
templateUrl: entityViewsTemplate,
controller: 'EntityViewController',
controllerAs: 'vm'
}
},
data: {
entityViewsType: 'tenant',
searchEnabled: true,
searchByEntitySubtype: true,
searchEntityType: types.entityType.entityView,
pageTitle: 'entity-view.entity-views'
},
ncyBreadcrumb: {
label: '{"icon": "view_stream", "label": "entity-view.entity-views"}'
}
})
.state('home.customers.entityViews', {
url: '/:customerId/entityViews',
params: {'topIndex': 0},
module: 'private',
auth: ['TENANT_ADMIN'],
views: {
"content@home": {
templateUrl: entityViewsTemplate,
controllerAs: 'vm',
controller: 'EntityViewController'
}
},
data: {
entityViewsType: 'customer',
searchEnabled: true,
searchByEntitySubtype: true,
searchEntityType: types.entityType.entityView,
pageTitle: 'customer.entity-views'
},
ncyBreadcrumb: {
label: '{"icon": "view_stream", "label": "{{ vm.customerEntityViewsTitle }}", "translate": "false"}'
}
});
}

75
ui/src/app/entity-view/entity-views.tpl.html

@ -0,0 +1,75 @@
<!--
Copyright © 2016-2018 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tb-grid grid-configuration="vm.entityViewGridConfig">
<details-buttons tb-help="'entityViews'" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
<md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
id="tabs" md-border-bottom flex class="tb-absolute-fill">
<md-tab label="{{ 'entity-view.details' | translate }}">
<tb-entity-view entity-view="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
entity-view-scope="vm.entityViewsScope"
the-form="vm.grid.detailsForm"
on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
on-delete-entity-view="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-entity-view>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
<tb-attribute-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.entityView}}"
entity-name="vm.grid.operatingItem().name"
default-attribute-scope="{{vm.types.attributesScope.client.value}}">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
<tb-attribute-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.entityView}}"
entity-name="vm.grid.operatingItem().name"
default-attribute-scope="{{vm.types.latestTelemetry.value}}"
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.entityView"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'entity-view.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.entityView"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.entityView}}">
</tb-relation-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
<tb-audit-log-table flex entity-type="vm.types.entityType.entityView"
entity-id="vm.grid.operatingItem().id.id"
audit-log-mode="{{vm.types.auditLogMode.entity}}">
</tb-audit-log-table>
</md-tab>
</tb-grid>

41
ui/src/app/entity-view/index.js

@ -0,0 +1,41 @@
/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardApiUser from '../api/user.service';
import thingsboardApiEntityView from '../api/entity-view.service';
import thingsboardApiCustomer from '../api/customer.service';
import EntityViewRoutes from './entity-view.routes';
import {EntityViewController, EntityViewCardController} from './entity-view.controller';
import AssignEntityViewToCustomerController from './assign-to-customer.controller';
import AddEntityViewsToCustomerController from './add-entity-views-to-customer.controller';
import EntityViewDirective from './entity-view.directive';
export default angular.module('thingsboard.entityView', [
uiRouter,
thingsboardGrid,
thingsboardApiUser,
thingsboardApiEntityView,
thingsboardApiCustomer
])
.config(EntityViewRoutes)
.controller('EntityViewController', EntityViewController)
.controller('EntityViewCardController', EntityViewCardController)
.controller('AssignEntityViewToCustomerController', AssignEntityViewToCustomerController)
.controller('AddEntityViewsToCustomerController', AddEntityViewsToCustomerController)
.directive('tbEntityView', EntityViewDirective)
.name;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save