Browse Source

Ability to assign dashboards to multiple customers - DAO Layer.

pull/619/head
Igor Kulikov 9 years ago
parent
commit
063a1093e0
  1. 23
      application/src/main/data/upgrade/1.4.0/schema_update.cql
  2. 10
      application/src/main/data/upgrade/1.4.0/schema_update.sql
  3. 12
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 67
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  5. 34
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  6. 93
      application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
  7. 6
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  8. 34
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
  10. 146
      application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java
  11. 118
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  12. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  13. 63
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  14. 3
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
  15. 4
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  16. 32
      dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
  17. 4
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
  18. 10
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  19. 159
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  20. 4
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  21. 51
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java
  22. 49
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java
  23. 32
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
  24. 33
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java
  25. 50
      dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java
  26. 3
      dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
  27. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
  28. 36
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
  29. 17
      dao/src/main/resources/cassandra/schema.cql
  30. 2
      dao/src/main/resources/sql/schema.sql
  31. 102
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
  32. 38
      dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java

23
application/src/main/data/upgrade/1.4.0/schema_update.cql

@ -87,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
PRIMARY KEY (( tenant_id ), partition)
) WITH CLUSTERING ORDER BY ( partition ASC )
AND compaction = { 'class' : 'LeveledCompactionStrategy' };
DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_customer_and_search_text;
DROP TABLE IF EXISTS thingsboard.dashboard;
CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
id timeuuid,
tenant_id timeuuid,
title text,
search_text text,
assigned_customers text,
configuration text,
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
SELECT *
from thingsboard.dashboard
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );

10
application/src/main/data/upgrade/1.4.0/schema_update.sql

@ -29,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log (
action_failure_details varchar(1000000)
);
DROP TABLE IF EXISTS dashboard;
CREATE TABLE IF NOT EXISTS dashboard (
id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
configuration varchar(10000000),
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id varchar(31),
title varchar(255)
);

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

@ -423,7 +423,7 @@ public abstract class BaseController {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
checkDashboard(dashboard, true);
checkDashboard(dashboard);
return dashboard;
} catch (Exception e) {
throw handleException(e, false);
@ -435,27 +435,23 @@ public abstract class BaseController {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
SecurityUser authUser = getCurrentUser();
checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
checkDashboard(dashboardInfo);
return dashboardInfo;
} catch (Exception e) {
throw handleException(e, false);
}
}
private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
checkNotNull(dashboard);
checkTenantId(dashboard.getTenantId());
SecurityUser authUser = getCurrentUser();
if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
if (dashboard.getAssignedCustomers() == null || !dashboard.getAssignedCustomers().containsKey(authUser.getCustomerId().toString())) {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
}
}
if (checkCustomerId &&
dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(dashboard.getCustomerId());
}
}
ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {

67
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.DashboardId;
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.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
logEntityAction(savedDashboard.getId(), savedDashboard,
savedDashboard.getCustomerId(),
null,
dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedDashboard;
@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
dashboardService.deleteDashboard(dashboardId);
logEntityAction(dashboardId, dashboard,
dashboard.getCustomerId(),
null,
ActionType.DELETED, null, strDashboardId);
} catch (Exception e) {
@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
logEntityAction(dashboardId, savedDashboard,
savedDashboard.getCustomerId(),
customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
public Dashboard unassignDashboardFromCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");
}
Customer customer = checkCustomerId(dashboard.getCustomerId());
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
dashboard.getCustomerId(),
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
return savedDashboard;
@ -192,7 +193,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, savedDashboard,
savedDashboard.getCustomerId(),
publicCustomer.getId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
@ -206,6 +207,33 @@ public class DashboardController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, dashboard,
publicCustomer.getId(),
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
@ -245,19 +273,20 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
public TextPageData<DashboardInfo> getCustomerDashboards(
public TimePageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String offset) 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(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
} catch (Exception e) {
throw handleException(e);
}

34
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.service.install.cql.CQLStatementsParser;
import org.thingsboard.server.service.install.cql.CassandraDbHelper;
@ -33,6 +34,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static org.thingsboard.server.service.install.DatabaseHelper.*;
@Service
@NoSqlDao
@Profile("install")
@ -40,12 +43,6 @@ import java.util.List;
public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
public static final String DEVICE = "device";
public static final String TENANT_ID = "tenant_id";
public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset";
@Value("${install.data_dir}")
private String dataDir;
@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
@Autowired
private CassandraInstallCluster installCluster;
@Autowired
private DashboardService dashboardService;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
case "1.3.0":
break;
case "1.3.1":
cluster.getSession();
ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping dashboards ...");
Path dashboardsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DASHBOARD,
new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
new String[]{"", "", "", "", "", "", ""},
"tb-dashboards");
log.info("Dashboards dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
log.info("Restoring dashboards ...");
if (dashboardsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD,
new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump);
DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false);
Files.deleteIfExists(dashboardsDump);
}
log.info("Dashboards restored.");
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

93
application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java

@ -0,0 +1,93 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.dao.dashboard.DashboardService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
/**
* Created by igor on 2/27/18.
*/
@Slf4j
public class DatabaseHelper {
public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
public static final String DEVICE = "device";
public static final String TENANT_ID = "tenant_id";
public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset";
public static final String DASHBOARD = "dashboard";
public static final String ID = "id";
public static final String TITLE = "title";
public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
public static final String CONFIGURATION = "configuration";
public static final ObjectMapper objectMapper = new ObjectMapper();
public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION};
try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withHeader(columns))) {
csvParser.forEach(record -> {
String customerIdString = record.get(CUSTOMER_ID);
String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS);
DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql));
List<CustomerId> customerIds = new ArrayList<>();
if (!StringUtils.isEmpty(assignedCustomersString)) {
try {
JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString);
Map<String,String> assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class);
assignedCustomers.forEach((strCustomerId, title) -> {
customerIds.add(new CustomerId(UUID.fromString(strCustomerId)));
});
} catch (IOException e) {
log.error("Unable to parse assigned customers field", e);
}
}
if (!StringUtils.isEmpty(customerIdString)) {
customerIds.add(new CustomerId(toUUID(customerIdString, sql)));
}
for (CustomerId customerId : customerIds) {
dashboardService.assignDashboardToCustomer(dashboardId, customerId);
}
});
}
}
private static UUID toUUID(String src, boolean sql) {
if (sql) {
return UUIDConverter.fromString(src);
} else {
return UUID.fromString(src);
}
}
}

6
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -339,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
JsonNode dashboardJson = objectMapper.readTree(path.toFile());
Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
dashboard.setTenantId(tenantId);
dashboard.setCustomerId(customerId);
dashboardService.saveDashboard(dashboard);
Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null && !customerId.isNullUid()) {
dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
}
} catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString());
throw new RuntimeException("Unable to load dashboard from json", e);

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

@ -17,18 +17,26 @@
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.dashboard.DashboardService;
import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.service.install.cql.CassandraDbHelper;
import org.thingsboard.server.service.install.sql.SqlDbHelper;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import static org.thingsboard.server.service.install.DatabaseHelper.*;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
@Service
@Profile("install")
@Slf4j
@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
@Value("${spring.datasource.password}")
private String dbPassword;
@Autowired
private DashboardService dashboardService;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
break;
case "1.3.1":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Dumping dashboards ...");
Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD,
new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
new String[]{"", "", "", "", "", "", ""},
"tb-dashboards");
log.info("Dashboards dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
log.info("Schema updated.");
log.info("Restoring dashboards ...");
if (dashboardsDump != null) {
SqlDbHelper.loadTable(conn, DASHBOARD,
new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump);
DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true);
Files.deleteIfExists(dashboardsDump);
}
log.info("Dashboards restored.");
}
log.info("Schema updated.");
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);

5
application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java

@ -17,7 +17,6 @@
package org.thingsboard.server.service.install.cql;
import com.datastax.driver.core.*;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
@ -28,9 +27,9 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
public class CassandraDbHelper {
import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
private static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
public class CassandraDbHelper {
public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {

146
application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java

@ -0,0 +1,146 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install.sql;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
/**
* Created by igor on 2/27/18.
*/
@Slf4j
public class SqlDbHelper {
public static Path dumpTableIfExists(Connection conn, String tableName,
String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
DatabaseMetaData metaData = conn.getMetaData();
ResultSet res = metaData.getTables(null, null, tableName,
new String[] {"TABLE"});
if (res.next()) {
res.close();
Path dumpFile = Files.createTempFile(dumpPrefix, null);
Files.deleteIfExists(dumpFile);
try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) {
try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) {
try (ResultSet tableRes = stmt.executeQuery()) {
ResultSetMetaData resMetaData = tableRes.getMetaData();
Map<String, Integer> columnIndexMap = new HashMap<>();
for (int i = 0; i < resMetaData.getColumnCount(); i++) {
String columnName = resMetaData.getColumnName(i);
columnIndexMap.put(columnName, i);
}
while(tableRes.next()) {
dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter);
}
}
}
}
return dumpFile;
} else {
return null;
}
}
public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception {
PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns));
prepared.getParameterMetaData();
try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) {
csvParser.forEach(record -> {
try {
for (int i=0;i<columns.length;i++) {
setColumnValue(i, columns[i], record, prepared);
}
prepared.execute();
} catch (SQLException e) {
log.error("Unable to load table record!", e);
}
});
}
}
private static void dumpRow(ResultSet res, Map<String, Integer> columnIndexMap, String[] columns,
String[] defaultValues, CSVPrinter csvPrinter) throws Exception {
List<String> record = new ArrayList<>();
for (int i=0;i<columns.length;i++) {
String column = columns[i];
String defaultValue;
if (defaultValues != null && i < defaultValues.length) {
defaultValue = defaultValues[i];
} else {
defaultValue = "";
}
record.add(getColumnValue(column, defaultValue, columnIndexMap, res));
}
csvPrinter.printRecord(record);
}
private static String getColumnValue(String column, String defaultValue, Map<String, Integer> columnIndexMap, ResultSet res) {
int index = columnIndexMap.containsKey(column) ? columnIndexMap.get(column) : -1;
if (index > -1) {
String str;
try {
Object obj = res.getObject(index);
if (obj == null) {
str = "";
} else {
str = obj.toString();
}
} catch (Exception e) {
str = "";
}
return str;
} else {
return defaultValue;
}
}
private static void setColumnValue(int index, String column,
CSVRecord record, PreparedStatement preparedStatement) throws SQLException {
String value = record.get(column);
int type = preparedStatement.getParameterMetaData().getParameterType(index + 1);
preparedStatement.setObject(index + 1, value, type);
}
private static String createInsertStatement(String tableName, String[] columns) {
StringBuilder insertStatementBuilder = new StringBuilder();
insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" (");
for (String column : columns) {
insertStatementBuilder.append(column).append(",");
}
insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
insertStatementBuilder.append(") VALUES (");
for (String column : columns) {
insertStatementBuilder.append("?").append(",");
}
insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
insertStatementBuilder.append(")");
return insertStatementBuilder.toString();
}
}

118
application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java

@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import org.junit.After;
@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
Assert.assertNotNull(savedDashboard.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
savedDashboard.setTitle("My new dashboard");
@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(savedCustomer.getId(), assignedDashboard.getCustomerId());
Assert.assertTrue(assignedDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString()));
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId());
Assert.assertTrue(foundDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString()));
Dashboard unassignedDashboard =
doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId());
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(ModelConstants.NULL_UUID, foundDashboard.getCustomerId().getId());
Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty());
}
@Test
@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
}
List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(21);
TextPageData<DashboardInfo> pageData = null;
TimePageLink pageLink = new TimePageLink(21);
TimePageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertEquals(dashboards, loadedDashboards);
}
@Test
public void testFindCustomerDashboardsByTitle() throws Exception {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1";
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<125;i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
String title2 = "Dashboard title 2";
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<143;i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(18, title1);
TextPageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle1, idComparator);
Collections.sort(loadedDashboardsTitle1, idComparator);
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(7, title2);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle2, idComparator);
Collections.sort(loadedDashboardsTitle2, idComparator);
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(5, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(9, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
}

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

@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo {
StringBuilder builder = new StringBuilder();
builder.append("Dashboard [tenantId=");
builder.append(getTenantId());
builder.append(", customerId=");
builder.append(getCustomerId());
builder.append(", title=");
builder.append(getTitle());
builder.append(", configuration=");

63
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -20,11 +20,16 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
private TenantId tenantId;
private CustomerId customerId;
private String title;
private Map<String, String> assignedCustomers;
public DashboardInfo() {
super();
@ -37,8 +42,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
public DashboardInfo(DashboardInfo dashboardInfo) {
super(dashboardInfo);
this.tenantId = dashboardInfo.getTenantId();
this.customerId = dashboardInfo.getCustomerId();
this.title = dashboardInfo.getTitle();
this.assignedCustomers = dashboardInfo.getAssignedCustomers();
}
public TenantId getTenantId() {
@ -49,14 +54,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.tenantId = tenantId;
}
public CustomerId getCustomerId() {
return customerId;
}
public void setCustomerId(CustomerId customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
@ -65,6 +62,44 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.title = title;
}
public Map<String, String> getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(Map<String, String> assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
public boolean addAssignedCustomer(CustomerId customerId, String title) {
if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
return false;
} else {
if (this.assignedCustomers == null) {
this.assignedCustomers = new HashMap<>();
}
this.assignedCustomers.put(customerId.toString(), title);
return true;
}
}
public boolean updateAssignedCustomer(CustomerId customerId, String title) {
if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
this.assignedCustomers.put(customerId.toString(), title);
return true;
} else {
return false;
}
}
public boolean removeAssignedCustomer(CustomerId customerId) {
if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
this.assignedCustomers.remove(customerId.toString());
return true;
} else {
return false;
}
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
@ -80,7 +115,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
@ -95,11 +129,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
if (getClass() != obj.getClass())
return false;
DashboardInfo other = (DashboardInfo) obj;
if (customerId == null) {
if (other.customerId != null)
return false;
} else if (!customerId.equals(other.customerId))
return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
@ -118,8 +147,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
StringBuilder builder = new StringBuilder();
builder.append("DashboardInfo [tenantId=");
builder.append(tenantId);
builder.append(", customerId=");
builder.append(customerId);
builder.append(", title=");
builder.append(title);
builder.append("]");

3
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.relation;
public enum RelationTypeGroup {
COMMON,
ALARM
ALARM,
DASHBOARD
}

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

@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
public Customer saveCustomer(Customer customer) {
log.trace("Executing saveCustomer [{}]", customer);
customerValidator.validate(customer);
return customerDao.save(customer);
Customer savedCustomer = customerDao.save(customer);
dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle());
return savedCustomer;
}
@Override

32
dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java

@ -15,16 +15,26 @@
*/
package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@NoSqlDao
public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Autowired
private RelationDao relationDao;
@Override
protected Class<DashboardInfoEntity> getColumnFamilyClass() {
return DashboardInfoEntity.class;
@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
}
@Override
public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
List<DashboardInfoEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Arrays.asList(eq(DASHBOARD_CUSTOMER_ID_PROPERTY, customerId),
eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)),
pageLink);
log.trace("Found dashboards [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", dashboardEntities, tenantId, customerId, pageLink);
return DaoUtil.convertDataList(dashboardEntities);
ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
for (EntityRelation relation : input) {
dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
}
return Futures.successfulAsList(dashboardFutures);
});
}
}

4
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java

@ -15,8 +15,10 @@
*/
package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
* @param pageLink the page link
* @return the list of dashboard objects
*/
List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink);
}

10
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -23,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId;
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.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.sql.Time;
public interface DashboardService {
@ -38,7 +42,7 @@ public interface DashboardService {
Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
Dashboard unassignDashboardFromCustomer(DashboardId dashboardId);
Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
void deleteDashboard(DashboardId dashboardId);
@ -46,8 +50,10 @@ public interface DashboardService {
void deleteDashboardsByTenantId(TenantId tenantId);
TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle);
}

159
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -15,30 +15,42 @@
*/
package org.thingsboard.server.dao.dashboard;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
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.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
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.model.ModelConstants;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.TimePaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.sql.Time;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private CustomerDao customerDao;
@Override
public Dashboard findDashboardById(DashboardId dashboardId) {
log.trace("Executing findDashboardById [{}]", dashboardId);
@ -98,15 +110,59 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Override
public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(dashboardId);
dashboard.setCustomerId(customerId);
return saveDashboard(dashboard);
Customer customer = customerDao.findById(customerId.getId());
if (customer == null) {
throw new DataValidationException("Can't assign dashboard to non-existent customer!");
}
if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
}
if (dashboard.addAssignedCustomer(customerId, customer.getTitle())) {
try {
createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to create dashboard relation. Customer Id: [{}]", dashboardId, customerId);
throw new RuntimeException(e);
}
return saveDashboard(dashboard);
} else {
return dashboard;
}
}
@Override
public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) {
public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(dashboardId);
dashboard.setCustomerId(null);
return saveDashboard(dashboard);
if (dashboard.removeAssignedCustomer(customerId)) {
try {
deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to delete dashboard relation. Customer Id: [{}]", dashboardId, customerId);
throw new RuntimeException(e);
}
return saveDashboard(dashboard);
} else {
return dashboard;
}
}
private Dashboard updateAssignedCustomerTitle(DashboardId dashboardId, CustomerId customerId, String customerTitle) {
Dashboard dashboard = findDashboardById(dashboardId);
if (dashboard.updateAssignedCustomer(customerId, customerTitle)) {
return saveDashboard(dashboard);
} else {
return dashboard;
}
}
private void deleteRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
log.debug("Deleting Dashboard relation: {}", dashboardRelation);
relationService.deleteRelationAsync(dashboardRelation).get();
}
private void createRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
log.debug("Creating Dashboard relation: {}", dashboardRelation);
relationService.saveRelationAsync(dashboardRelation).get();
}
@Override
@ -134,13 +190,20 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
}
@Override
public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
public ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
return new TextPageData<>(dashboards, pageLink);
ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
return Futures.transform(dashboards, new Function<List<DashboardInfo>, TimePageData<DashboardInfo>>() {
@Nullable
@Override
public TimePageData<DashboardInfo> apply(@Nullable List<DashboardInfo> dashboards) {
return new TimePageData<>(dashboards, pageLink);
}
});
}
@Override
@ -148,9 +211,18 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId);
new CustomerDashboardsUnassigner(tenantId, customerId).removeEntities(customerId);
}
@Override
public void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle) {
log.trace("Executing updateCustomerDashboards, tenantId [{}], customerId [{}], customerTitle [{}]", tenantId, customerId, customerTitle);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
Validator.validateString(customerTitle, "Incorrect customerTitle " + customerTitle);
new CustomerDashboardsUpdater(tenantId, customerId, customerTitle).removeEntities(customerId);
}
private DataValidator<Dashboard> dashboardValidator =
new DataValidator<Dashboard>() {
@Override
@ -166,17 +238,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
}
}
if (dashboard.getCustomerId() == null) {
dashboard.setCustomerId(new CustomerId(ModelConstants.NULL_UUID));
} else if (!dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
Customer customer = customerDao.findById(dashboard.getCustomerId().getId());
if (customer == null) {
throw new DataValidationException("Can't assign dashboard to non-existent customer!");
}
if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
}
}
}
};
@ -194,24 +255,60 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
}
};
private class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfo> {
private class CustomerDashboardsUnassigner extends TimePaginatedRemover<CustomerId, DashboardInfo> {
private TenantId tenantId;
private CustomerId customerId;
CustomerDashboardsUnassigner(TenantId tenantId) {
CustomerDashboardsUnassigner(TenantId tenantId, CustomerId customerId) {
this.tenantId = tenantId;
this.customerId = customerId;
}
@Override
protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
try {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get();
} catch (InterruptedException | ExecutionException e) {
log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id);
throw new RuntimeException(e);
}
}
@Override
protected void removeEntity(DashboardInfo entity) {
unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()));
unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customerId);
}
}
private class CustomerDashboardsUpdater extends TimePaginatedRemover<CustomerId, DashboardInfo> {
private TenantId tenantId;
private CustomerId customerId;
private String customerTitle;
CustomerDashboardsUpdater(TenantId tenantId, CustomerId customerId, String customerTitle) {
this.tenantId = tenantId;
this.customerId = customerId;
this.customerTitle = customerTitle;
}
@Override
protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
try {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get();
} catch (InterruptedException | ExecutionException e) {
log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id);
throw new RuntimeException(e);
}
}
@Override
protected void removeEntity(DashboardInfo entity) {
updateAssignedCustomerTitle(new DashboardId(entity.getUuidId()), this.customerId, this.customerTitle);
}
}
}

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

@ -266,13 +266,11 @@ public class ModelConstants {
*/
public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DASHBOARD_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
public static final String DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";
/**
* Cassandra plugin metadata constants.

51
dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java

@ -19,16 +19,19 @@ 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.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.model.type.JsonCodec;
import java.util.HashMap;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@ -36,8 +39,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
@EqualsAndHashCode
@ToString
@Slf4j
public final class DashboardEntity implements SearchTextEntity<Dashboard> {
private static final ObjectMapper objectMapper = new ObjectMapper();
@PartitionKey(value = 0)
@Column(name = ID_PROPERTY)
private UUID id;
@ -46,16 +52,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
@Column(name = DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)
private JsonNode assignedCustomers;
@Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
private JsonNode configuration;
@ -70,10 +75,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
if (dashboard.getTenantId() != null) {
this.tenantId = dashboard.getTenantId().getId();
}
if (dashboard.getCustomerId() != null) {
this.customerId = dashboard.getCustomerId().getId();
}
this.title = dashboard.getTitle();
if (dashboard.getAssignedCustomers() != null) {
this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers());
}
this.configuration = dashboard.getConfiguration();
}
@ -93,14 +98,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
this.tenantId = tenantId;
}
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
@ -109,6 +106,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
this.title = title;
}
public JsonNode getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(JsonNode assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
public JsonNode getConfiguration() {
return configuration;
}
@ -138,10 +143,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
if (tenantId != null) {
dashboard.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
dashboard.setCustomerId(new CustomerId(customerId));
}
dashboard.setTitle(title);
if (assignedCustomers != null) {
try {
dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
} catch (JsonProcessingException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
dashboard.setConfiguration(configuration);
return dashboard;
}

49
dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java

@ -19,14 +19,21 @@ 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.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.model.type.JsonCodec;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@ -34,8 +41,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
@EqualsAndHashCode
@ToString
@Slf4j
public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
private static final ObjectMapper objectMapper = new ObjectMapper();
@PartitionKey(value = 0)
@Column(name = ID_PROPERTY)
private UUID id;
@ -44,16 +54,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
@Column(name = DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)
private JsonNode assignedCustomers;
public DashboardInfoEntity() {
super();
}
@ -65,10 +74,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
if (dashboardInfo.getTenantId() != null) {
this.tenantId = dashboardInfo.getTenantId().getId();
}
if (dashboardInfo.getCustomerId() != null) {
this.customerId = dashboardInfo.getCustomerId().getId();
}
this.title = dashboardInfo.getTitle();
if (dashboardInfo.getAssignedCustomers() != null) {
this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers());
}
}
public UUID getId() {
@ -87,14 +96,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
this.tenantId = tenantId;
}
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
@ -103,6 +104,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
this.title = title;
}
public JsonNode getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(JsonNode assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
@Override
public String getSearchTextSource() {
return getTitle();
@ -124,10 +133,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(customerId));
}
dashboardInfo.setTitle(title);
if (assignedCustomers != null) {
try {
dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
} catch (JsonProcessingException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
return dashboardInfo;
}

32
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java

@ -16,9 +16,12 @@
package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.JsonProcessingException;
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.Dashboard;
@ -33,26 +36,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
private String tenantId;
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private JsonNode assignedCustomers;
@Type(type = "json")
@Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
private JsonNode configuration;
@ -68,10 +78,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
if (dashboard.getTenantId() != null) {
this.tenantId = toString(dashboard.getTenantId().getId());
}
if (dashboard.getCustomerId() != null) {
this.customerId = toString(dashboard.getCustomerId().getId());
}
this.title = dashboard.getTitle();
if (dashboard.getAssignedCustomers() != null) {
this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers());
}
this.configuration = dashboard.getConfiguration();
}
@ -92,10 +102,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
if (tenantId != null) {
dashboard.setTenantId(new TenantId(toUUID(tenantId)));
}
if (customerId != null) {
dashboard.setCustomerId(new CustomerId(toUUID(customerId)));
}
dashboard.setTitle(title);
if (assignedCustomers != null) {
try {
dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
} catch (JsonProcessingException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
dashboard.setConfiguration(configuration);
return dashboard;
}

33
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java

@ -16,8 +16,13 @@
package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.JsonProcessingException;
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.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@ -29,25 +34,31 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.HashMap;
import java.util.Set;
@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
private String tenantId;
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private JsonNode assignedCustomers;
public DashboardInfoEntity() {
super();
}
@ -59,10 +70,10 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
if (dashboardInfo.getTenantId() != null) {
this.tenantId = toString(dashboardInfo.getTenantId().getId());
}
if (dashboardInfo.getCustomerId() != null) {
this.customerId = toString(dashboardInfo.getCustomerId().getId());
}
this.title = dashboardInfo.getTitle();
if (dashboardInfo.getAssignedCustomers() != null) {
this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers());
}
}
@Override
@ -86,10 +97,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
}
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));
}
dashboardInfo.setTitle(title);
if (assignedCustomers != null) {
try {
dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
} catch (JsonProcessingException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
return dashboardInfo;
}

50
dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.sql.Time;
import java.util.List;
import java.util.UUID;
public abstract class TimePaginatedRemover<I, D extends IdBased<?>> {
private static final int DEFAULT_LIMIT = 100;
public void removeEntities(I id) {
TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT);
boolean hasNext = true;
while (hasNext) {
List<D> entities = findEntities(id, pageLink);
for (D entity : entities) {
removeEntity(entity);
}
hasNext = entities.size() == pageLink.getLimit();
if (hasNext) {
int index = entities.size() - 1;
UUID idOffset = entities.get(index).getUuidId();
pageLink.setIdOffset(idOffset);
}
}
}
protected abstract List<D> findEntities(I id, TimePageLink pageLink);
protected abstract void removeEntity(D entity);
}

3
dao/src/main/java/org/thingsboard/server/dao/service/Validator.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.BasePageLink;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -116,7 +117,7 @@ public class Validator {
* @param pageLink the page link
* @param errorMessage the error message for exception
*/
public static void validatePageLink(TextPageLink pageLink, String errorMessage) {
public static void validatePageLink(BasePageLink pageLink, String errorMessage) {
if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) {
throw new IncorrectParameterException(errorMessage);
}

8
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java

@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt
@Param("idOffset") String idOffset,
Pageable pageable);
@Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +
"AND di.customerId = :customerId AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
"AND di.id > :idOffset ORDER BY di.id")
List<DashboardInfoEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
@Param("customerId") String customerId,
@Param("searchText") String searchText,
@Param("idOffset") String idOffset,
Pageable pageable);
}

36
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java

@ -15,19 +15,31 @@
*/
package org.thingsboard.server.dao.sql.dashboard;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
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.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@ -37,10 +49,14 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
/**
* Created by Valerii Sosliuk on 5/6/2017.
*/
@Slf4j
@Component
@SqlDao
public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Autowired
private RelationDao relationDao;
@Autowired
private DashboardInfoRepository dashboardInfoRepository;
@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
}
@Override
public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
return DaoUtil.convertDataList(dashboardInfoRepository
.findByTenantIdAndCustomerId(
UUIDConverter.fromTimeUUID(tenantId),
UUIDConverter.fromTimeUUID(customerId),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
for (EntityRelation relation : input) {
dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
}
return Futures.successfulAsList(dashboardFutures);
});
}
}

17
dao/src/main/resources/cassandra/schema.cql

@ -364,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali
CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
id timeuuid,
tenant_id timeuuid,
customer_id timeuuid,
title text,
search_text text,
assigned_customers text,
configuration text,
PRIMARY KEY (id, tenant_id, customer_id)
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
SELECT *
from thingsboard.dashboard
WHERE tenant_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 )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
SELECT *
from thingsboard.dashboard
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( customer_id, tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
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)

2
dao/src/main/resources/sql/schema.sql

@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer (
CREATE TABLE IF NOT EXISTS dashboard (
id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
configuration varchar(10000000),
customer_id varchar(31),
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id varchar(31),
title varchar(255)

102
dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java

@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId;
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.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import java.io.IOException;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
Assert.assertNotNull(savedDashboard.getCustomerId());
Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
savedDashboard.setTitle("My new dashboard");
@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
}
@Test
public void testFindDashboardsByTenantIdAndCustomerId() {
public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant);
@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
}
List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23);
TextPageData<DashboardInfo> pageData = null;
TimePageLink pageLink = new TimePageLink(23);
TimePageData<DashboardInfo> pageData = null;
do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -320,96 +322,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
dashboardService.unassignCustomerDashboards(tenantId, customerId);
pageLink = new TextPageLink(42);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
pageLink = new TimePageLink(42);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId);
}
@Test
public void testFindDashboardsByTenantIdCustomerIdAndTitle() {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer.setTenantId(tenantId);
customer = customerService.saveCustomer(customer);
CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1";
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<124;i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = dashboardService.saveDashboard(dashboard);
dashboardsTitle1.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
}
String title2 = "Dashboard title 2";
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<151;i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = dashboardService.saveDashboard(dashboard);
dashboardsTitle2.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
}
List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(24, title1);
TextPageData<DashboardInfo> pageData = null;
do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle1, idComparator);
Collections.sort(loadedDashboardsTitle1, idComparator);
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2);
do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle2, idComparator);
Collections.sort(loadedDashboardsTitle2, idComparator);
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
dashboardService.deleteDashboard(dashboard.getId());
}
pageLink = new TextPageLink(4, title1);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
dashboardService.deleteDashboard(dashboard.getId());
}
pageLink = new TextPageLink(4, title2);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
customerService.deleteCustomer(customerId);
}
}

38
dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.sql.dashboard;
import com.datastax.driver.core.utils.UUIDs;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.DashboardInfo;
@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
@Test
public void testFindDashboardsByTenantId() {
UUID tenantId1 = UUIDs.timeBased();
UUID customerId1 = UUIDs.timeBased();
UUID tenantId2 = UUIDs.timeBased();
UUID customerId2 = UUIDs.timeBased();
for (int i = 0; i < 20; i++) {
createDashboard(tenantId1, customerId1, i);
createDashboard(tenantId2, customerId2, i * 2);
createDashboard(tenantId1, i);
createDashboard(tenantId2, i * 2);
}
TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1);
assertEquals(15, dashboardInfos1.size());
Assert.assertEquals(15, dashboardInfos1.size());
TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2);
assertEquals(5, dashboardInfos2.size());
Assert.assertEquals(5, dashboardInfos2.size());
}
@Test
public void testFindDashboardsByTenantAndCustomerId() {
UUID tenantId1 = UUIDs.timeBased();
UUID customerId1 = UUIDs.timeBased();
UUID tenantId2 = UUIDs.timeBased();
UUID customerId2 = UUIDs.timeBased();
for (int i = 0; i < 20; i++) {
createDashboard(tenantId1, customerId1, i);
createDashboard(tenantId2, customerId2, i * 2);
}
TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1);
assertEquals(15, dashboardInfos1.size());
TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink2);
assertEquals(5, dashboardInfos2.size());
}
private void assertEquals(int i, int size) {
}
private void createDashboard(UUID tenantId, UUID customerId, int index) {
private void createDashboard(UUID tenantId, int index) {
DashboardInfo dashboardInfo = new DashboardInfo();
dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
dashboardInfo.setTenantId(new TenantId(tenantId));
dashboardInfo.setCustomerId(new CustomerId(customerId));
dashboardInfo.setTitle("DASHBOARD_" + index);
dashboardInfoDao.save(dashboardInfo);
}

Loading…
Cancel
Save