Commit 063a1093e0179ae7840d0f8e3bce3afdeeb08dad

Authored by Igor Kulikov
1 parent b90f0ea0

Ability to assign dashboards to multiple customers - DAO Layer.

Showing 32 changed files with 830 additions and 420 deletions
@@ -87,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( @@ -87,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
87 PRIMARY KEY (( tenant_id ), partition) 87 PRIMARY KEY (( tenant_id ), partition)
88 ) WITH CLUSTERING ORDER BY ( partition ASC ) 88 ) WITH CLUSTERING ORDER BY ( partition ASC )
89 AND compaction = { 'class' : 'LeveledCompactionStrategy' }; 89 AND compaction = { 'class' : 'LeveledCompactionStrategy' };
  90 +
  91 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_tenant_and_search_text;
  92 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_customer_and_search_text;
  93 +
  94 +DROP TABLE IF EXISTS thingsboard.dashboard;
  95 +
  96 +CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
  97 + id timeuuid,
  98 + tenant_id timeuuid,
  99 + title text,
  100 + search_text text,
  101 + assigned_customers text,
  102 + configuration text,
  103 + PRIMARY KEY (id, tenant_id)
  104 +);
  105 +
  106 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
  107 + SELECT *
  108 + from thingsboard.dashboard
  109 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  110 + PRIMARY KEY ( tenant_id, search_text, id )
  111 + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
  112 +
@@ -29,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log ( @@ -29,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log (
29 action_failure_details varchar(1000000) 29 action_failure_details varchar(1000000)
30 ); 30 );
31 31
  32 +DROP TABLE IF EXISTS dashboard;
  33 +
  34 +CREATE TABLE IF NOT EXISTS dashboard (
  35 + id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
  36 + configuration varchar(10000000),
  37 + assigned_customers varchar(1000000),
  38 + search_text varchar(255),
  39 + tenant_id varchar(31),
  40 + title varchar(255)
  41 +);
@@ -423,7 +423,7 @@ public abstract class BaseController { @@ -423,7 +423,7 @@ public abstract class BaseController {
423 try { 423 try {
424 validateId(dashboardId, "Incorrect dashboardId " + dashboardId); 424 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
425 Dashboard dashboard = dashboardService.findDashboardById(dashboardId); 425 Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
426 - checkDashboard(dashboard, true); 426 + checkDashboard(dashboard);
427 return dashboard; 427 return dashboard;
428 } catch (Exception e) { 428 } catch (Exception e) {
429 throw handleException(e, false); 429 throw handleException(e, false);
@@ -435,27 +435,23 @@ public abstract class BaseController { @@ -435,27 +435,23 @@ public abstract class BaseController {
435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId); 435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId); 436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
437 SecurityUser authUser = getCurrentUser(); 437 SecurityUser authUser = getCurrentUser();
438 - checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN); 438 + checkDashboard(dashboardInfo);
439 return dashboardInfo; 439 return dashboardInfo;
440 } catch (Exception e) { 440 } catch (Exception e) {
441 throw handleException(e, false); 441 throw handleException(e, false);
442 } 442 }
443 } 443 }
444 444
445 - private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException { 445 + private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
446 checkNotNull(dashboard); 446 checkNotNull(dashboard);
447 checkTenantId(dashboard.getTenantId()); 447 checkTenantId(dashboard.getTenantId());
448 SecurityUser authUser = getCurrentUser(); 448 SecurityUser authUser = getCurrentUser();
449 if (authUser.getAuthority() == Authority.CUSTOMER_USER) { 449 if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
450 - if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { 450 + if (dashboard.getAssignedCustomers() == null || !dashboard.getAssignedCustomers().containsKey(authUser.getCustomerId().toString())) {
451 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, 451 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
452 ThingsboardErrorCode.PERMISSION_DENIED); 452 ThingsboardErrorCode.PERMISSION_DENIED);
453 } 453 }
454 } 454 }
455 - if (checkCustomerId &&  
456 - dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {  
457 - checkCustomerId(dashboard.getCustomerId());  
458 - }  
459 } 455 }
460 456
461 ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { 457 ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
@@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.DashboardId; @@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.DashboardId;
28 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
29 import org.thingsboard.server.common.data.page.TextPageData; 29 import org.thingsboard.server.common.data.page.TextPageData;
30 import org.thingsboard.server.common.data.page.TextPageLink; 30 import org.thingsboard.server.common.data.page.TextPageLink;
  31 +import org.thingsboard.server.common.data.page.TimePageData;
  32 +import org.thingsboard.server.common.data.page.TimePageLink;
31 import org.thingsboard.server.dao.exception.IncorrectParameterException; 33 import org.thingsboard.server.dao.exception.IncorrectParameterException;
32 import org.thingsboard.server.dao.model.ModelConstants; 34 import org.thingsboard.server.dao.model.ModelConstants;
33 import org.thingsboard.server.exception.ThingsboardException; 35 import org.thingsboard.server.exception.ThingsboardException;
@@ -80,7 +82,7 @@ public class DashboardController extends BaseController { @@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
80 Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); 82 Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
81 83
82 logEntityAction(savedDashboard.getId(), savedDashboard, 84 logEntityAction(savedDashboard.getId(), savedDashboard,
83 - savedDashboard.getCustomerId(), 85 + null,
84 dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); 86 dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
85 87
86 return savedDashboard; 88 return savedDashboard;
@@ -103,7 +105,7 @@ public class DashboardController extends BaseController { @@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
103 dashboardService.deleteDashboard(dashboardId); 105 dashboardService.deleteDashboard(dashboardId);
104 106
105 logEntityAction(dashboardId, dashboard, 107 logEntityAction(dashboardId, dashboard,
106 - dashboard.getCustomerId(), 108 + null,
107 ActionType.DELETED, null, strDashboardId); 109 ActionType.DELETED, null, strDashboardId);
108 110
109 } catch (Exception e) { 111 } catch (Exception e) {
@@ -134,7 +136,7 @@ public class DashboardController extends BaseController { @@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
134 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); 136 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
135 137
136 logEntityAction(dashboardId, savedDashboard, 138 logEntityAction(dashboardId, savedDashboard,
137 - savedDashboard.getCustomerId(), 139 + customerId,
138 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName()); 140 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
139 141
140 142
@@ -150,23 +152,22 @@ public class DashboardController extends BaseController { @@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
150 } 152 }
151 153
152 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 154 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
153 - @RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE) 155 + @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
154 @ResponseBody 156 @ResponseBody
155 - public Dashboard unassignDashboardFromCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { 157 + public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
  158 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  159 + checkParameter("customerId", strCustomerId);
156 checkParameter(DASHBOARD_ID, strDashboardId); 160 checkParameter(DASHBOARD_ID, strDashboardId);
157 try { 161 try {
  162 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  163 + Customer customer = checkCustomerId(customerId);
158 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); 164 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
159 Dashboard dashboard = checkDashboardId(dashboardId); 165 Dashboard dashboard = checkDashboardId(dashboardId);
160 - if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {  
161 - throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");  
162 - }  
163 -  
164 - Customer customer = checkCustomerId(dashboard.getCustomerId());  
165 166
166 - Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId)); 167 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
167 168
168 logEntityAction(dashboardId, dashboard, 169 logEntityAction(dashboardId, dashboard,
169 - dashboard.getCustomerId(), 170 + customerId,
170 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName()); 171 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
171 172
172 return savedDashboard; 173 return savedDashboard;
@@ -192,7 +193,7 @@ public class DashboardController extends BaseController { @@ -192,7 +193,7 @@ public class DashboardController extends BaseController {
192 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); 193 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
193 194
194 logEntityAction(dashboardId, savedDashboard, 195 logEntityAction(dashboardId, savedDashboard,
195 - savedDashboard.getCustomerId(), 196 + publicCustomer.getId(),
196 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName()); 197 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
197 198
198 return savedDashboard; 199 return savedDashboard;
@@ -206,6 +207,33 @@ public class DashboardController extends BaseController { @@ -206,6 +207,33 @@ public class DashboardController extends BaseController {
206 } 207 }
207 } 208 }
208 209
  210 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  211 + @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  212 + @ResponseBody
  213 + public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  214 + checkParameter(DASHBOARD_ID, strDashboardId);
  215 + try {
  216 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  217 + Dashboard dashboard = checkDashboardId(dashboardId);
  218 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
  219 +
  220 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, publicCustomer.getId()));
  221 +
  222 + logEntityAction(dashboardId, dashboard,
  223 + publicCustomer.getId(),
  224 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
  225 +
  226 + return savedDashboard;
  227 + } catch (Exception e) {
  228 +
  229 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  230 + null,
  231 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
  232 +
  233 + throw handleException(e);
  234 + }
  235 + }
  236 +
209 @PreAuthorize("hasAuthority('SYS_ADMIN')") 237 @PreAuthorize("hasAuthority('SYS_ADMIN')")
210 @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) 238 @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
211 @ResponseBody 239 @ResponseBody
@@ -245,19 +273,20 @@ public class DashboardController extends BaseController { @@ -245,19 +273,20 @@ public class DashboardController extends BaseController {
245 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 273 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
246 @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) 274 @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
247 @ResponseBody 275 @ResponseBody
248 - public TextPageData<DashboardInfo> getCustomerDashboards( 276 + public TimePageData<DashboardInfo> getCustomerDashboards(
249 @PathVariable("customerId") String strCustomerId, 277 @PathVariable("customerId") String strCustomerId,
250 @RequestParam int limit, 278 @RequestParam int limit,
251 - @RequestParam(required = false) String textSearch,  
252 - @RequestParam(required = false) String idOffset,  
253 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 279 + @RequestParam(required = false) Long startTime,
  280 + @RequestParam(required = false) Long endTime,
  281 + @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
  282 + @RequestParam(required = false) String offset) throws ThingsboardException {
254 checkParameter("customerId", strCustomerId); 283 checkParameter("customerId", strCustomerId);
255 try { 284 try {
256 TenantId tenantId = getCurrentUser().getTenantId(); 285 TenantId tenantId = getCurrentUser().getTenantId();
257 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 286 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
258 checkCustomerId(customerId); 287 checkCustomerId(customerId);
259 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);  
260 - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); 288 + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
  289 + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
261 } catch (Exception e) { 290 } catch (Exception e) {
262 throw handleException(e); 291 throw handleException(e);
263 } 292 }
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile; @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
25 import org.thingsboard.server.dao.cassandra.CassandraCluster; 25 import org.thingsboard.server.dao.cassandra.CassandraCluster;
26 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; 26 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
  27 +import org.thingsboard.server.dao.dashboard.DashboardService;
27 import org.thingsboard.server.dao.util.NoSqlDao; 28 import org.thingsboard.server.dao.util.NoSqlDao;
28 import org.thingsboard.server.service.install.cql.CQLStatementsParser; 29 import org.thingsboard.server.service.install.cql.CQLStatementsParser;
29 import org.thingsboard.server.service.install.cql.CassandraDbHelper; 30 import org.thingsboard.server.service.install.cql.CassandraDbHelper;
@@ -33,6 +34,8 @@ import java.nio.file.Path; @@ -33,6 +34,8 @@ import java.nio.file.Path;
33 import java.nio.file.Paths; 34 import java.nio.file.Paths;
34 import java.util.List; 35 import java.util.List;
35 36
  37 +import static org.thingsboard.server.service.install.DatabaseHelper.*;
  38 +
36 @Service 39 @Service
37 @NoSqlDao 40 @NoSqlDao
38 @Profile("install") 41 @Profile("install")
@@ -40,12 +43,6 @@ import java.util.List; @@ -40,12 +43,6 @@ import java.util.List;
40 public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { 43 public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
41 44
42 private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; 45 private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
43 - public static final String DEVICE = "device";  
44 - public static final String TENANT_ID = "tenant_id";  
45 - public static final String CUSTOMER_ID = "customer_id";  
46 - public static final String SEARCH_TEXT = "search_text";  
47 - public static final String ADDITIONAL_INFO = "additional_info";  
48 - public static final String ASSET = "asset";  
49 46
50 @Value("${install.data_dir}") 47 @Value("${install.data_dir}")
51 private String dataDir; 48 private String dataDir;
@@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
56 @Autowired 53 @Autowired
57 private CassandraInstallCluster installCluster; 54 private CassandraInstallCluster installCluster;
58 55
  56 + @Autowired
  57 + private DashboardService dashboardService;
  58 +
59 @Override 59 @Override
60 public void upgradeDatabase(String fromVersion) throws Exception { 60 public void upgradeDatabase(String fromVersion) throws Exception {
61 61
@@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
160 case "1.3.0": 160 case "1.3.0":
161 break; 161 break;
162 case "1.3.1": 162 case "1.3.1":
  163 +
  164 + cluster.getSession();
  165 +
  166 + ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
  167 +
  168 + log.info("Dumping dashboards ...");
  169 + Path dashboardsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DASHBOARD,
  170 + new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
  171 + new String[]{"", "", "", "", "", "", ""},
  172 + "tb-dashboards");
  173 + log.info("Dashboards dumped.");
  174 +
  175 +
163 log.info("Updating schema ..."); 176 log.info("Updating schema ...");
164 schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); 177 schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
165 loadCql(schemaUpdateFile); 178 loadCql(schemaUpdateFile);
166 log.info("Schema updated."); 179 log.info("Schema updated.");
  180 +
  181 + log.info("Restoring dashboards ...");
  182 + if (dashboardsDump != null) {
  183 + CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD,
  184 + new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump);
  185 + DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false);
  186 + Files.deleteIfExists(dashboardsDump);
  187 + }
  188 + log.info("Dashboards restored.");
167 break; 189 break;
168 default: 190 default:
169 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); 191 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.install;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.commons.csv.CSVFormat;
  21 +import org.apache.commons.csv.CSVParser;
  22 +import org.apache.commons.lang3.StringUtils;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import org.thingsboard.server.common.data.UUIDConverter;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.DashboardId;
  27 +import org.thingsboard.server.dao.dashboard.DashboardService;
  28 +
  29 +import java.io.IOException;
  30 +import java.nio.file.Files;
  31 +import java.nio.file.Path;
  32 +import java.util.*;
  33 +
  34 +/**
  35 + * Created by igor on 2/27/18.
  36 + */
  37 +@Slf4j
  38 +public class DatabaseHelper {
  39 +
  40 + public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
  41 +
  42 + public static final String DEVICE = "device";
  43 + public static final String TENANT_ID = "tenant_id";
  44 + public static final String CUSTOMER_ID = "customer_id";
  45 + public static final String SEARCH_TEXT = "search_text";
  46 + public static final String ADDITIONAL_INFO = "additional_info";
  47 + public static final String ASSET = "asset";
  48 + public static final String DASHBOARD = "dashboard";
  49 + public static final String ID = "id";
  50 + public static final String TITLE = "title";
  51 + public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
  52 + public static final String CONFIGURATION = "configuration";
  53 +
  54 + public static final ObjectMapper objectMapper = new ObjectMapper();
  55 +
  56 + public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
  57 + String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION};
  58 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withHeader(columns))) {
  59 + csvParser.forEach(record -> {
  60 + String customerIdString = record.get(CUSTOMER_ID);
  61 + String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS);
  62 + DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql));
  63 + List<CustomerId> customerIds = new ArrayList<>();
  64 + if (!StringUtils.isEmpty(assignedCustomersString)) {
  65 + try {
  66 + JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString);
  67 + Map<String,String> assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class);
  68 + assignedCustomers.forEach((strCustomerId, title) -> {
  69 + customerIds.add(new CustomerId(UUID.fromString(strCustomerId)));
  70 + });
  71 + } catch (IOException e) {
  72 + log.error("Unable to parse assigned customers field", e);
  73 + }
  74 + }
  75 + if (!StringUtils.isEmpty(customerIdString)) {
  76 + customerIds.add(new CustomerId(toUUID(customerIdString, sql)));
  77 + }
  78 + for (CustomerId customerId : customerIds) {
  79 + dashboardService.assignDashboardToCustomer(dashboardId, customerId);
  80 + }
  81 + });
  82 + }
  83 + }
  84 +
  85 + private static UUID toUUID(String src, boolean sql) {
  86 + if (sql) {
  87 + return UUIDConverter.fromString(src);
  88 + } else {
  89 + return UUID.fromString(src);
  90 + }
  91 + }
  92 +
  93 +}
@@ -339,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -339,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
339 JsonNode dashboardJson = objectMapper.readTree(path.toFile()); 339 JsonNode dashboardJson = objectMapper.readTree(path.toFile());
340 Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class); 340 Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
341 dashboard.setTenantId(tenantId); 341 dashboard.setTenantId(tenantId);
342 - dashboard.setCustomerId(customerId);  
343 - dashboardService.saveDashboard(dashboard); 342 + Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
  343 + if (customerId != null && !customerId.isNullUid()) {
  344 + dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
  345 + }
344 } catch (Exception e) { 346 } catch (Exception e) {
345 log.error("Unable to load dashboard from json: [{}]", path.toString()); 347 log.error("Unable to load dashboard from json: [{}]", path.toString());
346 throw new RuntimeException("Unable to load dashboard from json", e); 348 throw new RuntimeException("Unable to load dashboard from json", e);
@@ -17,18 +17,26 @@ @@ -17,18 +17,26 @@
17 package org.thingsboard.server.service.install; 17 package org.thingsboard.server.service.install;
18 18
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.beans.factory.annotation.Value; 21 import org.springframework.beans.factory.annotation.Value;
21 import org.springframework.context.annotation.Profile; 22 import org.springframework.context.annotation.Profile;
22 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.dao.dashboard.DashboardService;
23 import org.thingsboard.server.dao.util.SqlDao; 25 import org.thingsboard.server.dao.util.SqlDao;
  26 +import org.thingsboard.server.service.install.cql.CassandraDbHelper;
  27 +import org.thingsboard.server.service.install.sql.SqlDbHelper;
24 28
25 import java.nio.charset.Charset; 29 import java.nio.charset.Charset;
26 import java.nio.file.Files; 30 import java.nio.file.Files;
27 import java.nio.file.Path; 31 import java.nio.file.Path;
28 import java.nio.file.Paths; 32 import java.nio.file.Paths;
29 import java.sql.Connection; 33 import java.sql.Connection;
  34 +import java.sql.DatabaseMetaData;
30 import java.sql.DriverManager; 35 import java.sql.DriverManager;
31 36
  37 +import static org.thingsboard.server.service.install.DatabaseHelper.*;
  38 +import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
  39 +
32 @Service 40 @Service
33 @Profile("install") 41 @Profile("install")
34 @Slf4j 42 @Slf4j
@@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
49 @Value("${spring.datasource.password}") 57 @Value("${spring.datasource.password}")
50 private String dbPassword; 58 private String dbPassword;
51 59
  60 + @Autowired
  61 + private DashboardService dashboardService;
  62 +
52 @Override 63 @Override
53 public void upgradeDatabase(String fromVersion) throws Exception { 64 public void upgradeDatabase(String fromVersion) throws Exception {
54 switch (fromVersion) { 65 switch (fromVersion) {
@@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
62 log.info("Schema updated."); 73 log.info("Schema updated.");
63 break; 74 break;
64 case "1.3.1": 75 case "1.3.1":
65 - log.info("Updating schema ...");  
66 - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);  
67 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 76 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  77 +
  78 + log.info("Dumping dashboards ...");
  79 + Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD,
  80 + new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
  81 + new String[]{"", "", "", "", "", "", ""},
  82 + "tb-dashboards");
  83 + log.info("Dashboards dumped.");
  84 +
  85 + log.info("Updating schema ...");
  86 + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
68 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); 87 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
69 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script 88 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  89 + log.info("Schema updated.");
  90 +
  91 + log.info("Restoring dashboards ...");
  92 + if (dashboardsDump != null) {
  93 + SqlDbHelper.loadTable(conn, DASHBOARD,
  94 + new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump);
  95 + DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true);
  96 + Files.deleteIfExists(dashboardsDump);
  97 + }
  98 + log.info("Dashboards restored.");
70 } 99 }
71 - log.info("Schema updated.");  
72 break; 100 break;
73 default: 101 default:
74 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 102 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
@@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
17 package org.thingsboard.server.service.install.cql; 17 package org.thingsboard.server.service.install.cql;
18 18
19 import com.datastax.driver.core.*; 19 import com.datastax.driver.core.*;
20 -import org.apache.commons.csv.CSVFormat;  
21 import org.apache.commons.csv.CSVParser; 20 import org.apache.commons.csv.CSVParser;
22 import org.apache.commons.csv.CSVPrinter; 21 import org.apache.commons.csv.CSVPrinter;
23 import org.apache.commons.csv.CSVRecord; 22 import org.apache.commons.csv.CSVRecord;
@@ -28,9 +27,9 @@ import java.nio.file.Path; @@ -28,9 +27,9 @@ import java.nio.file.Path;
28 import java.nio.file.StandardCopyOption; 27 import java.nio.file.StandardCopyOption;
29 import java.util.*; 28 import java.util.*;
30 29
31 -public class CassandraDbHelper { 30 +import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
32 31
33 - private static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N"); 32 +public class CassandraDbHelper {
34 33
35 public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName, 34 public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
36 String[] columns, String[] defaultValues, String dumpPrefix) throws Exception { 35 String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.install.sql;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.apache.commons.csv.CSVParser;
  20 +import org.apache.commons.csv.CSVPrinter;
  21 +import org.apache.commons.csv.CSVRecord;
  22 +
  23 +import java.nio.file.Files;
  24 +import java.nio.file.Path;
  25 +import java.sql.*;
  26 +import java.util.ArrayList;
  27 +import java.util.HashMap;
  28 +import java.util.List;
  29 +import java.util.Map;
  30 +
  31 +import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
  32 +
  33 +/**
  34 + * Created by igor on 2/27/18.
  35 + */
  36 +@Slf4j
  37 +public class SqlDbHelper {
  38 +
  39 + public static Path dumpTableIfExists(Connection conn, String tableName,
  40 + String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
  41 +
  42 + DatabaseMetaData metaData = conn.getMetaData();
  43 + ResultSet res = metaData.getTables(null, null, tableName,
  44 + new String[] {"TABLE"});
  45 + if (res.next()) {
  46 + res.close();
  47 + Path dumpFile = Files.createTempFile(dumpPrefix, null);
  48 + Files.deleteIfExists(dumpFile);
  49 + try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) {
  50 + try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) {
  51 + try (ResultSet tableRes = stmt.executeQuery()) {
  52 + ResultSetMetaData resMetaData = tableRes.getMetaData();
  53 + Map<String, Integer> columnIndexMap = new HashMap<>();
  54 + for (int i = 0; i < resMetaData.getColumnCount(); i++) {
  55 + String columnName = resMetaData.getColumnName(i);
  56 + columnIndexMap.put(columnName, i);
  57 + }
  58 + while(tableRes.next()) {
  59 + dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter);
  60 + }
  61 + }
  62 + }
  63 + }
  64 + return dumpFile;
  65 + } else {
  66 + return null;
  67 + }
  68 + }
  69 +
  70 + public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception {
  71 + PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns));
  72 + prepared.getParameterMetaData();
  73 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) {
  74 + csvParser.forEach(record -> {
  75 + try {
  76 + for (int i=0;i<columns.length;i++) {
  77 + setColumnValue(i, columns[i], record, prepared);
  78 + }
  79 + prepared.execute();
  80 + } catch (SQLException e) {
  81 + log.error("Unable to load table record!", e);
  82 + }
  83 + });
  84 + }
  85 + }
  86 +
  87 + private static void dumpRow(ResultSet res, Map<String, Integer> columnIndexMap, String[] columns,
  88 + String[] defaultValues, CSVPrinter csvPrinter) throws Exception {
  89 + List<String> record = new ArrayList<>();
  90 + for (int i=0;i<columns.length;i++) {
  91 + String column = columns[i];
  92 + String defaultValue;
  93 + if (defaultValues != null && i < defaultValues.length) {
  94 + defaultValue = defaultValues[i];
  95 + } else {
  96 + defaultValue = "";
  97 + }
  98 + record.add(getColumnValue(column, defaultValue, columnIndexMap, res));
  99 + }
  100 + csvPrinter.printRecord(record);
  101 + }
  102 +
  103 + private static String getColumnValue(String column, String defaultValue, Map<String, Integer> columnIndexMap, ResultSet res) {
  104 + int index = columnIndexMap.containsKey(column) ? columnIndexMap.get(column) : -1;
  105 + if (index > -1) {
  106 + String str;
  107 + try {
  108 + Object obj = res.getObject(index);
  109 + if (obj == null) {
  110 + str = "";
  111 + } else {
  112 + str = obj.toString();
  113 + }
  114 + } catch (Exception e) {
  115 + str = "";
  116 + }
  117 + return str;
  118 + } else {
  119 + return defaultValue;
  120 + }
  121 + }
  122 +
  123 + private static void setColumnValue(int index, String column,
  124 + CSVRecord record, PreparedStatement preparedStatement) throws SQLException {
  125 + String value = record.get(column);
  126 + int type = preparedStatement.getParameterMetaData().getParameterType(index + 1);
  127 + preparedStatement.setObject(index + 1, value, type);
  128 + }
  129 +
  130 + private static String createInsertStatement(String tableName, String[] columns) {
  131 + StringBuilder insertStatementBuilder = new StringBuilder();
  132 + insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" (");
  133 + for (String column : columns) {
  134 + insertStatementBuilder.append(column).append(",");
  135 + }
  136 + insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
  137 + insertStatementBuilder.append(") VALUES (");
  138 + for (String column : columns) {
  139 + insertStatementBuilder.append("?").append(",");
  140 + }
  141 + insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
  142 + insertStatementBuilder.append(")");
  143 + return insertStatementBuilder.toString();
  144 + }
  145 +
  146 +}
@@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.containsString; @@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.containsString;
19 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; 19 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
20 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21 21
  22 +import java.sql.Time;
22 import java.util.ArrayList; 23 import java.util.ArrayList;
23 import java.util.Collections; 24 import java.util.Collections;
24 import java.util.List; 25 import java.util.List;
@@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*; @@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*;
29 import org.thingsboard.server.common.data.id.CustomerId; 30 import org.thingsboard.server.common.data.id.CustomerId;
30 import org.thingsboard.server.common.data.page.TextPageData; 31 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 32 import org.thingsboard.server.common.data.page.TextPageLink;
  33 +import org.thingsboard.server.common.data.page.TimePageData;
  34 +import org.thingsboard.server.common.data.page.TimePageLink;
32 import org.thingsboard.server.common.data.security.Authority; 35 import org.thingsboard.server.common.data.security.Authority;
33 import org.thingsboard.server.dao.model.ModelConstants; 36 import org.thingsboard.server.dao.model.ModelConstants;
34 import org.junit.After; 37 import org.junit.After;
@@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
82 Assert.assertNotNull(savedDashboard.getId()); 85 Assert.assertNotNull(savedDashboard.getId());
83 Assert.assertTrue(savedDashboard.getCreatedTime() > 0); 86 Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
84 Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId()); 87 Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
85 - Assert.assertNotNull(savedDashboard.getCustomerId());  
86 - Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());  
87 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); 88 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
88 89
89 savedDashboard.setTitle("My new dashboard"); 90 savedDashboard.setTitle("My new dashboard");
@@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
136 137
137 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() 138 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
138 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 139 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
139 - Assert.assertEquals(savedCustomer.getId(), assignedDashboard.getCustomerId());  
140 - 140 +
  141 + Assert.assertTrue(assignedDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString()));
  142 +
141 Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 143 Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
142 - Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId()); 144 + Assert.assertTrue(foundDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString()));
143 145
144 Dashboard unassignedDashboard = 146 Dashboard unassignedDashboard =
145 - doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);  
146 - Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId());  
147 - 147 + doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
  148 +
  149 + Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
  150 +
148 foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 151 foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
149 - Assert.assertEquals(ModelConstants.NULL_UUID, foundDashboard.getCustomerId().getId()); 152 +
  153 + Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty());
150 } 154 }
151 155
152 @Test 156 @Test
@@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
320 } 324 }
321 325
322 List<DashboardInfo> loadedDashboards = new ArrayList<>(); 326 List<DashboardInfo> loadedDashboards = new ArrayList<>();
323 - TextPageLink pageLink = new TextPageLink(21);  
324 - TextPageData<DashboardInfo> pageData = null; 327 + TimePageLink pageLink = new TimePageLink(21);
  328 + TimePageData<DashboardInfo> pageData = null;
325 do { 329 do {
326 - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",  
327 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); 330 + pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
  331 + new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink);
328 loadedDashboards.addAll(pageData.getData()); 332 loadedDashboards.addAll(pageData.getData());
329 if (pageData.hasNext()) { 333 if (pageData.hasNext()) {
330 pageLink = pageData.getNextPageLink(); 334 pageLink = pageData.getNextPageLink();
@@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
336 340
337 Assert.assertEquals(dashboards, loadedDashboards); 341 Assert.assertEquals(dashboards, loadedDashboards);
338 } 342 }
339 -  
340 - @Test  
341 - public void testFindCustomerDashboardsByTitle() throws Exception {  
342 - Customer customer = new Customer();  
343 - customer.setTitle("Test customer");  
344 - customer = doPost("/api/customer", customer, Customer.class);  
345 - CustomerId customerId = customer.getId();  
346 -  
347 - String title1 = "Dashboard title 1";  
348 - List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();  
349 - for (int i=0;i<125;i++) {  
350 - Dashboard dashboard = new Dashboard();  
351 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));  
352 - String title = title1+suffix;  
353 - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();  
354 - dashboard.setTitle(title);  
355 - dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);  
356 - dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()  
357 - + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));  
358 - }  
359 - String title2 = "Dashboard title 2";  
360 - List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();  
361 - for (int i=0;i<143;i++) {  
362 - Dashboard dashboard = new Dashboard();  
363 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));  
364 - String title = title2+suffix;  
365 - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();  
366 - dashboard.setTitle(title);  
367 - dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);  
368 - dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()  
369 - + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));  
370 - }  
371 -  
372 - List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();  
373 - TextPageLink pageLink = new TextPageLink(18, title1);  
374 - TextPageData<DashboardInfo> pageData = null;  
375 - do {  
376 - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",  
377 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);  
378 - loadedDashboardsTitle1.addAll(pageData.getData());  
379 - if (pageData.hasNext()) {  
380 - pageLink = pageData.getNextPageLink();  
381 - }  
382 - } while (pageData.hasNext());  
383 -  
384 - Collections.sort(dashboardsTitle1, idComparator);  
385 - Collections.sort(loadedDashboardsTitle1, idComparator);  
386 -  
387 - Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);  
388 -  
389 - List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();  
390 - pageLink = new TextPageLink(7, title2);  
391 - do {  
392 - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",  
393 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);  
394 - loadedDashboardsTitle2.addAll(pageData.getData());  
395 - if (pageData.hasNext()) {  
396 - pageLink = pageData.getNextPageLink();  
397 - }  
398 - } while (pageData.hasNext());  
399 -  
400 - Collections.sort(dashboardsTitle2, idComparator);  
401 - Collections.sort(loadedDashboardsTitle2, idComparator);  
402 -  
403 - Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);  
404 -  
405 - for (DashboardInfo dashboard : loadedDashboardsTitle1) {  
406 - doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())  
407 - .andExpect(status().isOk());  
408 - }  
409 -  
410 - pageLink = new TextPageLink(5, title1);  
411 - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",  
412 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);  
413 - Assert.assertFalse(pageData.hasNext());  
414 - Assert.assertEquals(0, pageData.getData().size());  
415 -  
416 - for (DashboardInfo dashboard : loadedDashboardsTitle2) {  
417 - doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())  
418 - .andExpect(status().isOk());  
419 - }  
420 -  
421 - pageLink = new TextPageLink(9, title2);  
422 - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",  
423 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);  
424 - Assert.assertFalse(pageData.hasNext());  
425 - Assert.assertEquals(0, pageData.getData().size());  
426 - }  
427 343
428 } 344 }
@@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo { @@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo {
79 StringBuilder builder = new StringBuilder(); 79 StringBuilder builder = new StringBuilder();
80 builder.append("Dashboard [tenantId="); 80 builder.append("Dashboard [tenantId=");
81 builder.append(getTenantId()); 81 builder.append(getTenantId());
82 - builder.append(", customerId=");  
83 - builder.append(getCustomerId());  
84 builder.append(", title="); 82 builder.append(", title=");
85 builder.append(getTitle()); 83 builder.append(getTitle());
86 builder.append(", configuration="); 84 builder.append(", configuration=");
@@ -20,11 +20,16 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -20,11 +20,16 @@ import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.DashboardId; 20 import org.thingsboard.server.common.data.id.DashboardId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 22
  23 +import java.util.HashMap;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +import java.util.Set;
  27 +
23 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName { 28 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
24 29
25 private TenantId tenantId; 30 private TenantId tenantId;
26 - private CustomerId customerId;  
27 private String title; 31 private String title;
  32 + private Map<String, String> assignedCustomers;
28 33
29 public DashboardInfo() { 34 public DashboardInfo() {
30 super(); 35 super();
@@ -37,8 +42,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -37,8 +42,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
37 public DashboardInfo(DashboardInfo dashboardInfo) { 42 public DashboardInfo(DashboardInfo dashboardInfo) {
38 super(dashboardInfo); 43 super(dashboardInfo);
39 this.tenantId = dashboardInfo.getTenantId(); 44 this.tenantId = dashboardInfo.getTenantId();
40 - this.customerId = dashboardInfo.getCustomerId();  
41 this.title = dashboardInfo.getTitle(); 45 this.title = dashboardInfo.getTitle();
  46 + this.assignedCustomers = dashboardInfo.getAssignedCustomers();
42 } 47 }
43 48
44 public TenantId getTenantId() { 49 public TenantId getTenantId() {
@@ -49,14 +54,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -49,14 +54,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
49 this.tenantId = tenantId; 54 this.tenantId = tenantId;
50 } 55 }
51 56
52 - public CustomerId getCustomerId() {  
53 - return customerId;  
54 - }  
55 -  
56 - public void setCustomerId(CustomerId customerId) {  
57 - this.customerId = customerId;  
58 - }  
59 -  
60 public String getTitle() { 57 public String getTitle() {
61 return title; 58 return title;
62 } 59 }
@@ -65,6 +62,44 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -65,6 +62,44 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
65 this.title = title; 62 this.title = title;
66 } 63 }
67 64
  65 + public Map<String, String> getAssignedCustomers() {
  66 + return assignedCustomers;
  67 + }
  68 +
  69 + public void setAssignedCustomers(Map<String, String> assignedCustomers) {
  70 + this.assignedCustomers = assignedCustomers;
  71 + }
  72 +
  73 + public boolean addAssignedCustomer(CustomerId customerId, String title) {
  74 + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
  75 + return false;
  76 + } else {
  77 + if (this.assignedCustomers == null) {
  78 + this.assignedCustomers = new HashMap<>();
  79 + }
  80 + this.assignedCustomers.put(customerId.toString(), title);
  81 + return true;
  82 + }
  83 + }
  84 +
  85 + public boolean updateAssignedCustomer(CustomerId customerId, String title) {
  86 + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
  87 + this.assignedCustomers.put(customerId.toString(), title);
  88 + return true;
  89 + } else {
  90 + return false;
  91 + }
  92 + }
  93 +
  94 + public boolean removeAssignedCustomer(CustomerId customerId) {
  95 + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
  96 + this.assignedCustomers.remove(customerId.toString());
  97 + return true;
  98 + } else {
  99 + return false;
  100 + }
  101 + }
  102 +
68 @Override 103 @Override
69 @JsonProperty(access = JsonProperty.Access.READ_ONLY) 104 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
70 public String getName() { 105 public String getName() {
@@ -80,7 +115,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -80,7 +115,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
80 public int hashCode() { 115 public int hashCode() {
81 final int prime = 31; 116 final int prime = 31;
82 int result = super.hashCode(); 117 int result = super.hashCode();
83 - result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());  
84 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); 118 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
85 result = prime * result + ((title == null) ? 0 : title.hashCode()); 119 result = prime * result + ((title == null) ? 0 : title.hashCode());
86 return result; 120 return result;
@@ -95,11 +129,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -95,11 +129,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
95 if (getClass() != obj.getClass()) 129 if (getClass() != obj.getClass())
96 return false; 130 return false;
97 DashboardInfo other = (DashboardInfo) obj; 131 DashboardInfo other = (DashboardInfo) obj;
98 - if (customerId == null) {  
99 - if (other.customerId != null)  
100 - return false;  
101 - } else if (!customerId.equals(other.customerId))  
102 - return false;  
103 if (tenantId == null) { 132 if (tenantId == null) {
104 if (other.tenantId != null) 133 if (other.tenantId != null)
105 return false; 134 return false;
@@ -118,8 +147,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -118,8 +147,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
118 StringBuilder builder = new StringBuilder(); 147 StringBuilder builder = new StringBuilder();
119 builder.append("DashboardInfo [tenantId="); 148 builder.append("DashboardInfo [tenantId=");
120 builder.append(tenantId); 149 builder.append(tenantId);
121 - builder.append(", customerId=");  
122 - builder.append(customerId);  
123 builder.append(", title="); 150 builder.append(", title=");
124 builder.append(title); 151 builder.append(title);
125 builder.append("]"); 152 builder.append("]");
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.relation; @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.relation;
18 public enum RelationTypeGroup { 18 public enum RelationTypeGroup {
19 19
20 COMMON, 20 COMMON,
21 - ALARM 21 + ALARM,
  22 + DASHBOARD
22 23
23 } 24 }
@@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
97 public Customer saveCustomer(Customer customer) { 97 public Customer saveCustomer(Customer customer) {
98 log.trace("Executing saveCustomer [{}]", customer); 98 log.trace("Executing saveCustomer [{}]", customer);
99 customerValidator.validate(customer); 99 customerValidator.validate(customer);
100 - return customerDao.save(customer); 100 + Customer savedCustomer = customerDao.save(customer);
  101 + dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle());
  102 + return savedCustomer;
101 } 103 }
102 104
103 @Override 105 @Override
@@ -15,16 +15,26 @@ @@ -15,16 +15,26 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
18 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.stereotype.Component; 23 import org.springframework.stereotype.Component;
20 import org.thingsboard.server.common.data.DashboardInfo; 24 import org.thingsboard.server.common.data.DashboardInfo;
  25 +import org.thingsboard.server.common.data.EntityType;
  26 +import org.thingsboard.server.common.data.id.CustomerId;
21 import org.thingsboard.server.common.data.page.TextPageLink; 27 import org.thingsboard.server.common.data.page.TextPageLink;
  28 +import org.thingsboard.server.common.data.page.TimePageLink;
  29 +import org.thingsboard.server.common.data.relation.EntityRelation;
  30 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
22 import org.thingsboard.server.dao.DaoUtil; 31 import org.thingsboard.server.dao.DaoUtil;
23 import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity; 32 import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity;
24 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; 33 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
  34 +import org.thingsboard.server.dao.relation.RelationDao;
25 import org.thingsboard.server.dao.util.NoSqlDao; 35 import org.thingsboard.server.dao.util.NoSqlDao;
26 36
27 -import java.util.Arrays; 37 +import java.util.ArrayList;
28 import java.util.Collections; 38 import java.util.Collections;
29 import java.util.List; 39 import java.util.List;
30 import java.util.UUID; 40 import java.util.UUID;
@@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
37 @NoSqlDao 47 @NoSqlDao
38 public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao { 48 public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
39 49
  50 + @Autowired
  51 + private RelationDao relationDao;
  52 +
40 @Override 53 @Override
41 protected Class<DashboardInfoEntity> getColumnFamilyClass() { 54 protected Class<DashboardInfoEntity> getColumnFamilyClass() {
42 return DashboardInfoEntity.class; 55 return DashboardInfoEntity.class;
@@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da @@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
59 } 72 }
60 73
61 @Override 74 @Override
62 - public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { 75 + public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
63 log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); 76 log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
64 - List<DashboardInfoEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,  
65 - Arrays.asList(eq(DASHBOARD_CUSTOMER_ID_PROPERTY, customerId),  
66 - eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)),  
67 - pageLink);  
68 77
69 - log.trace("Found dashboards [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", dashboardEntities, tenantId, customerId, pageLink);  
70 - return DaoUtil.convertDataList(dashboardEntities); 78 + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
  79 +
  80 + return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
  81 + List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
  82 + for (EntityRelation relation : input) {
  83 + dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
  84 + }
  85 + return Futures.successfulAsList(dashboardFutures);
  86 + });
71 } 87 }
72 88
73 } 89 }
@@ -15,8 +15,10 @@ @@ -15,8 +15,10 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.DashboardInfo; 19 import org.thingsboard.server.common.data.DashboardInfo;
19 import org.thingsboard.server.common.data.page.TextPageLink; 20 import org.thingsboard.server.common.data.page.TextPageLink;
  21 +import org.thingsboard.server.common.data.page.TimePageLink;
20 import org.thingsboard.server.dao.Dao; 22 import org.thingsboard.server.dao.Dao;
21 23
22 import java.util.List; 24 import java.util.List;
@@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> { @@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
44 * @param pageLink the page link 46 * @param pageLink the page link
45 * @return the list of dashboard objects 47 * @return the list of dashboard objects
46 */ 48 */
47 - List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); 49 + ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink);
48 50
49 } 51 }
@@ -23,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId; @@ -23,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId;
23 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
24 import org.thingsboard.server.common.data.page.TextPageData; 24 import org.thingsboard.server.common.data.page.TextPageData;
25 import org.thingsboard.server.common.data.page.TextPageLink; 25 import org.thingsboard.server.common.data.page.TextPageLink;
  26 +import org.thingsboard.server.common.data.page.TimePageData;
  27 +import org.thingsboard.server.common.data.page.TimePageLink;
  28 +
  29 +import java.sql.Time;
26 30
27 public interface DashboardService { 31 public interface DashboardService {
28 32
@@ -38,7 +42,7 @@ public interface DashboardService { @@ -38,7 +42,7 @@ public interface DashboardService {
38 42
39 Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId); 43 Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
40 44
41 - Dashboard unassignDashboardFromCustomer(DashboardId dashboardId); 45 + Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
42 46
43 void deleteDashboard(DashboardId dashboardId); 47 void deleteDashboard(DashboardId dashboardId);
44 48
@@ -46,8 +50,10 @@ public interface DashboardService { @@ -46,8 +50,10 @@ public interface DashboardService {
46 50
47 void deleteDashboardsByTenantId(TenantId tenantId); 51 void deleteDashboardsByTenantId(TenantId tenantId);
48 52
49 - TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); 53 + ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
50 54
51 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); 55 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
52 56
  57 + void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle);
  58 +
53 } 59 }
@@ -15,30 +15,42 @@ @@ -15,30 +15,42 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.base.Function;
  19 +import com.google.common.util.concurrent.Futures;
18 import com.google.common.util.concurrent.ListenableFuture; 20 import com.google.common.util.concurrent.ListenableFuture;
19 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
20 import org.apache.commons.lang3.StringUtils; 22 import org.apache.commons.lang3.StringUtils;
21 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
23 -import org.thingsboard.server.common.data.Customer;  
24 -import org.thingsboard.server.common.data.Dashboard;  
25 -import org.thingsboard.server.common.data.DashboardInfo;  
26 -import org.thingsboard.server.common.data.Tenant; 25 +import org.thingsboard.server.common.data.*;
  26 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
27 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
28 import org.thingsboard.server.common.data.id.DashboardId; 28 import org.thingsboard.server.common.data.id.DashboardId;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.page.TextPageData; 30 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 31 import org.thingsboard.server.common.data.page.TextPageLink;
  32 +import org.thingsboard.server.common.data.page.TimePageData;
  33 +import org.thingsboard.server.common.data.page.TimePageLink;
  34 +import org.thingsboard.server.common.data.relation.EntityRelation;
  35 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
32 import org.thingsboard.server.dao.customer.CustomerDao; 36 import org.thingsboard.server.dao.customer.CustomerDao;
33 import org.thingsboard.server.dao.entity.AbstractEntityService; 37 import org.thingsboard.server.dao.entity.AbstractEntityService;
34 import org.thingsboard.server.dao.exception.DataValidationException; 38 import org.thingsboard.server.dao.exception.DataValidationException;
35 import org.thingsboard.server.dao.model.ModelConstants; 39 import org.thingsboard.server.dao.model.ModelConstants;
  40 +import org.thingsboard.server.dao.relation.RelationDao;
36 import org.thingsboard.server.dao.service.DataValidator; 41 import org.thingsboard.server.dao.service.DataValidator;
37 import org.thingsboard.server.dao.service.PaginatedRemover; 42 import org.thingsboard.server.dao.service.PaginatedRemover;
  43 +import org.thingsboard.server.dao.service.TimePaginatedRemover;
38 import org.thingsboard.server.dao.service.Validator; 44 import org.thingsboard.server.dao.service.Validator;
39 import org.thingsboard.server.dao.tenant.TenantDao; 45 import org.thingsboard.server.dao.tenant.TenantDao;
40 46
  47 +import javax.annotation.Nullable;
  48 +import java.sql.Time;
  49 +import java.util.ArrayList;
  50 +import java.util.HashSet;
41 import java.util.List; 51 import java.util.List;
  52 +import java.util.Set;
  53 +import java.util.concurrent.ExecutionException;
42 54
43 import static org.thingsboard.server.dao.service.Validator.validateId; 55 import static org.thingsboard.server.dao.service.Validator.validateId;
44 56
@@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
59 71
60 @Autowired 72 @Autowired
61 private CustomerDao customerDao; 73 private CustomerDao customerDao;
62 - 74 +
63 @Override 75 @Override
64 public Dashboard findDashboardById(DashboardId dashboardId) { 76 public Dashboard findDashboardById(DashboardId dashboardId) {
65 log.trace("Executing findDashboardById [{}]", dashboardId); 77 log.trace("Executing findDashboardById [{}]", dashboardId);
@@ -98,15 +110,59 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -98,15 +110,59 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
98 @Override 110 @Override
99 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) { 111 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
100 Dashboard dashboard = findDashboardById(dashboardId); 112 Dashboard dashboard = findDashboardById(dashboardId);
101 - dashboard.setCustomerId(customerId);  
102 - return saveDashboard(dashboard); 113 + Customer customer = customerDao.findById(customerId.getId());
  114 + if (customer == null) {
  115 + throw new DataValidationException("Can't assign dashboard to non-existent customer!");
  116 + }
  117 + if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
  118 + throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
  119 + }
  120 + if (dashboard.addAssignedCustomer(customerId, customer.getTitle())) {
  121 + try {
  122 + createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
  123 + } catch (ExecutionException | InterruptedException e) {
  124 + log.warn("[{}] Failed to create dashboard relation. Customer Id: [{}]", dashboardId, customerId);
  125 + throw new RuntimeException(e);
  126 + }
  127 + return saveDashboard(dashboard);
  128 + } else {
  129 + return dashboard;
  130 + }
103 } 131 }
104 132
105 @Override 133 @Override
106 - public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) { 134 + public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
107 Dashboard dashboard = findDashboardById(dashboardId); 135 Dashboard dashboard = findDashboardById(dashboardId);
108 - dashboard.setCustomerId(null);  
109 - return saveDashboard(dashboard); 136 + if (dashboard.removeAssignedCustomer(customerId)) {
  137 + try {
  138 + deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
  139 + } catch (ExecutionException | InterruptedException e) {
  140 + log.warn("[{}] Failed to delete dashboard relation. Customer Id: [{}]", dashboardId, customerId);
  141 + throw new RuntimeException(e);
  142 + }
  143 + return saveDashboard(dashboard);
  144 + } else {
  145 + return dashboard;
  146 + }
  147 + }
  148 +
  149 + private Dashboard updateAssignedCustomerTitle(DashboardId dashboardId, CustomerId customerId, String customerTitle) {
  150 + Dashboard dashboard = findDashboardById(dashboardId);
  151 + if (dashboard.updateAssignedCustomer(customerId, customerTitle)) {
  152 + return saveDashboard(dashboard);
  153 + } else {
  154 + return dashboard;
  155 + }
  156 + }
  157 +
  158 + private void deleteRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
  159 + log.debug("Deleting Dashboard relation: {}", dashboardRelation);
  160 + relationService.deleteRelationAsync(dashboardRelation).get();
  161 + }
  162 +
  163 + private void createRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
  164 + log.debug("Creating Dashboard relation: {}", dashboardRelation);
  165 + relationService.saveRelationAsync(dashboardRelation).get();
110 } 166 }
111 167
112 @Override 168 @Override
@@ -134,13 +190,20 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -134,13 +190,20 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
134 } 190 }
135 191
136 @Override 192 @Override
137 - public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { 193 + public ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
138 log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); 194 log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
139 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 195 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
140 Validator.validateId(customerId, "Incorrect customerId " + customerId); 196 Validator.validateId(customerId, "Incorrect customerId " + customerId);
141 Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); 197 Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
142 - List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);  
143 - return new TextPageData<>(dashboards, pageLink); 198 + ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
  199 +
  200 + return Futures.transform(dashboards, new Function<List<DashboardInfo>, TimePageData<DashboardInfo>>() {
  201 + @Nullable
  202 + @Override
  203 + public TimePageData<DashboardInfo> apply(@Nullable List<DashboardInfo> dashboards) {
  204 + return new TimePageData<>(dashboards, pageLink);
  205 + }
  206 + });
144 } 207 }
145 208
146 @Override 209 @Override
@@ -148,9 +211,18 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -148,9 +211,18 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
148 log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId); 211 log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
149 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 212 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
150 Validator.validateId(customerId, "Incorrect customerId " + customerId); 213 Validator.validateId(customerId, "Incorrect customerId " + customerId);
151 - new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId); 214 + new CustomerDashboardsUnassigner(tenantId, customerId).removeEntities(customerId);
152 } 215 }
153 - 216 +
  217 + @Override
  218 + public void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle) {
  219 + log.trace("Executing updateCustomerDashboards, tenantId [{}], customerId [{}], customerTitle [{}]", tenantId, customerId, customerTitle);
  220 + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  221 + Validator.validateId(customerId, "Incorrect customerId " + customerId);
  222 + Validator.validateString(customerTitle, "Incorrect customerTitle " + customerTitle);
  223 + new CustomerDashboardsUpdater(tenantId, customerId, customerTitle).removeEntities(customerId);
  224 + }
  225 +
154 private DataValidator<Dashboard> dashboardValidator = 226 private DataValidator<Dashboard> dashboardValidator =
155 new DataValidator<Dashboard>() { 227 new DataValidator<Dashboard>() {
156 @Override 228 @Override
@@ -166,17 +238,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -166,17 +238,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
166 throw new DataValidationException("Dashboard is referencing to non-existent tenant!"); 238 throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
167 } 239 }
168 } 240 }
169 - if (dashboard.getCustomerId() == null) {  
170 - dashboard.setCustomerId(new CustomerId(ModelConstants.NULL_UUID));  
171 - } else if (!dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {  
172 - Customer customer = customerDao.findById(dashboard.getCustomerId().getId());  
173 - if (customer == null) {  
174 - throw new DataValidationException("Can't assign dashboard to non-existent customer!");  
175 - }  
176 - if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {  
177 - throw new DataValidationException("Can't assign dashboard to customer from different tenant!");  
178 - }  
179 - }  
180 } 241 }
181 }; 242 };
182 243
@@ -194,24 +255,60 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -194,24 +255,60 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
194 } 255 }
195 }; 256 };
196 257
197 - private class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfo> { 258 + private class CustomerDashboardsUnassigner extends TimePaginatedRemover<CustomerId, DashboardInfo> {
198 259
199 private TenantId tenantId; 260 private TenantId tenantId;
  261 + private CustomerId customerId;
200 262
201 - CustomerDashboardsUnassigner(TenantId tenantId) { 263 + CustomerDashboardsUnassigner(TenantId tenantId, CustomerId customerId) {
202 this.tenantId = tenantId; 264 this.tenantId = tenantId;
  265 + this.customerId = customerId;
203 } 266 }
204 267
205 @Override 268 @Override
206 - protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) {  
207 - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); 269 + protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
  270 + try {
  271 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get();
  272 + } catch (InterruptedException | ExecutionException e) {
  273 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id);
  274 + throw new RuntimeException(e);
  275 + }
208 } 276 }
209 277
210 @Override 278 @Override
211 protected void removeEntity(DashboardInfo entity) { 279 protected void removeEntity(DashboardInfo entity) {
212 - unassignDashboardFromCustomer(new DashboardId(entity.getUuidId())); 280 + unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customerId);
213 } 281 }
214 282
215 } 283 }
216 284
  285 + private class CustomerDashboardsUpdater extends TimePaginatedRemover<CustomerId, DashboardInfo> {
  286 +
  287 + private TenantId tenantId;
  288 + private CustomerId customerId;
  289 + private String customerTitle;
  290 +
  291 + CustomerDashboardsUpdater(TenantId tenantId, CustomerId customerId, String customerTitle) {
  292 + this.tenantId = tenantId;
  293 + this.customerId = customerId;
  294 + this.customerTitle = customerTitle;
  295 + }
  296 +
  297 + @Override
  298 + protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
  299 + try {
  300 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get();
  301 + } catch (InterruptedException | ExecutionException e) {
  302 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id);
  303 + throw new RuntimeException(e);
  304 + }
  305 + }
  306 +
  307 + @Override
  308 + protected void removeEntity(DashboardInfo entity) {
  309 + updateAssignedCustomerTitle(new DashboardId(entity.getUuidId()), this.customerId, this.customerTitle);
  310 + }
  311 +
  312 + }
  313 +
217 } 314 }
@@ -266,13 +266,11 @@ public class ModelConstants { @@ -266,13 +266,11 @@ public class ModelConstants {
266 */ 266 */
267 public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard"; 267 public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
268 public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; 268 public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
269 - public static final String DASHBOARD_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;  
270 public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY; 269 public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
271 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration"; 270 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
  271 + public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
272 272
273 public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text"; 273 public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
274 - public static final String DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";  
275 -  
276 274
277 /** 275 /**
278 * Cassandra plugin metadata constants. 276 * Cassandra plugin metadata constants.
@@ -19,16 +19,19 @@ import com.datastax.driver.core.utils.UUIDs; @@ -19,16 +19,19 @@ import com.datastax.driver.core.utils.UUIDs;
19 import com.datastax.driver.mapping.annotations.Column; 19 import com.datastax.driver.mapping.annotations.Column;
20 import com.datastax.driver.mapping.annotations.PartitionKey; 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 import com.datastax.driver.mapping.annotations.Table; 21 import com.datastax.driver.mapping.annotations.Table;
  22 +import com.fasterxml.jackson.core.JsonProcessingException;
22 import com.fasterxml.jackson.databind.JsonNode; 23 import com.fasterxml.jackson.databind.JsonNode;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
23 import lombok.EqualsAndHashCode; 25 import lombok.EqualsAndHashCode;
24 import lombok.ToString; 26 import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
25 import org.thingsboard.server.common.data.Dashboard; 28 import org.thingsboard.server.common.data.Dashboard;
26 -import org.thingsboard.server.common.data.id.CustomerId;  
27 import org.thingsboard.server.common.data.id.DashboardId; 29 import org.thingsboard.server.common.data.id.DashboardId;
28 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
29 import org.thingsboard.server.dao.model.SearchTextEntity; 31 import org.thingsboard.server.dao.model.SearchTextEntity;
30 import org.thingsboard.server.dao.model.type.JsonCodec; 32 import org.thingsboard.server.dao.model.type.JsonCodec;
31 33
  34 +import java.util.HashMap;
32 import java.util.UUID; 35 import java.util.UUID;
33 36
34 import static org.thingsboard.server.dao.model.ModelConstants.*; 37 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -36,8 +39,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -36,8 +39,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
36 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME) 39 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
37 @EqualsAndHashCode 40 @EqualsAndHashCode
38 @ToString 41 @ToString
  42 +@Slf4j
39 public final class DashboardEntity implements SearchTextEntity<Dashboard> { 43 public final class DashboardEntity implements SearchTextEntity<Dashboard> {
40 - 44 +
  45 + private static final ObjectMapper objectMapper = new ObjectMapper();
  46 +
41 @PartitionKey(value = 0) 47 @PartitionKey(value = 0)
42 @Column(name = ID_PROPERTY) 48 @Column(name = ID_PROPERTY)
43 private UUID id; 49 private UUID id;
@@ -46,16 +52,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -46,16 +52,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
46 @Column(name = DASHBOARD_TENANT_ID_PROPERTY) 52 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
47 private UUID tenantId; 53 private UUID tenantId;
48 54
49 - @PartitionKey(value = 2)  
50 - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)  
51 - private UUID customerId;  
52 -  
53 @Column(name = DASHBOARD_TITLE_PROPERTY) 55 @Column(name = DASHBOARD_TITLE_PROPERTY)
54 private String title; 56 private String title;
55 57
56 @Column(name = SEARCH_TEXT_PROPERTY) 58 @Column(name = SEARCH_TEXT_PROPERTY)
57 private String searchText; 59 private String searchText;
58 - 60 +
  61 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)
  62 + private JsonNode assignedCustomers;
  63 +
59 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) 64 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
60 private JsonNode configuration; 65 private JsonNode configuration;
61 66
@@ -70,10 +75,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -70,10 +75,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
70 if (dashboard.getTenantId() != null) { 75 if (dashboard.getTenantId() != null) {
71 this.tenantId = dashboard.getTenantId().getId(); 76 this.tenantId = dashboard.getTenantId().getId();
72 } 77 }
73 - if (dashboard.getCustomerId() != null) {  
74 - this.customerId = dashboard.getCustomerId().getId();  
75 - }  
76 this.title = dashboard.getTitle(); 78 this.title = dashboard.getTitle();
  79 + if (dashboard.getAssignedCustomers() != null) {
  80 + this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers());
  81 + }
77 this.configuration = dashboard.getConfiguration(); 82 this.configuration = dashboard.getConfiguration();
78 } 83 }
79 84
@@ -93,14 +98,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -93,14 +98,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
93 this.tenantId = tenantId; 98 this.tenantId = tenantId;
94 } 99 }
95 100
96 - public UUID getCustomerId() {  
97 - return customerId;  
98 - }  
99 -  
100 - public void setCustomerId(UUID customerId) {  
101 - this.customerId = customerId;  
102 - }  
103 -  
104 public String getTitle() { 101 public String getTitle() {
105 return title; 102 return title;
106 } 103 }
@@ -109,6 +106,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -109,6 +106,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
109 this.title = title; 106 this.title = title;
110 } 107 }
111 108
  109 + public JsonNode getAssignedCustomers() {
  110 + return assignedCustomers;
  111 + }
  112 +
  113 + public void setAssignedCustomers(JsonNode assignedCustomers) {
  114 + this.assignedCustomers = assignedCustomers;
  115 + }
  116 +
112 public JsonNode getConfiguration() { 117 public JsonNode getConfiguration() {
113 return configuration; 118 return configuration;
114 } 119 }
@@ -138,10 +143,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -138,10 +143,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
138 if (tenantId != null) { 143 if (tenantId != null) {
139 dashboard.setTenantId(new TenantId(tenantId)); 144 dashboard.setTenantId(new TenantId(tenantId));
140 } 145 }
141 - if (customerId != null) {  
142 - dashboard.setCustomerId(new CustomerId(customerId));  
143 - }  
144 dashboard.setTitle(title); 146 dashboard.setTitle(title);
  147 + if (assignedCustomers != null) {
  148 + try {
  149 + dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  150 + } catch (JsonProcessingException e) {
  151 + log.warn("Unable to parse assigned customers!", e);
  152 + }
  153 + }
145 dashboard.setConfiguration(configuration); 154 dashboard.setConfiguration(configuration);
146 return dashboard; 155 return dashboard;
147 } 156 }
@@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs; @@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs;
19 import com.datastax.driver.mapping.annotations.Column; 19 import com.datastax.driver.mapping.annotations.Column;
20 import com.datastax.driver.mapping.annotations.PartitionKey; 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 import com.datastax.driver.mapping.annotations.Table; 21 import com.datastax.driver.mapping.annotations.Table;
  22 +import com.fasterxml.jackson.core.JsonProcessingException;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
22 import lombok.EqualsAndHashCode; 25 import lombok.EqualsAndHashCode;
23 import lombok.ToString; 26 import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
24 import org.thingsboard.server.common.data.DashboardInfo; 28 import org.thingsboard.server.common.data.DashboardInfo;
25 import org.thingsboard.server.common.data.id.CustomerId; 29 import org.thingsboard.server.common.data.id.CustomerId;
26 import org.thingsboard.server.common.data.id.DashboardId; 30 import org.thingsboard.server.common.data.id.DashboardId;
27 import org.thingsboard.server.common.data.id.TenantId; 31 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.dao.model.SearchTextEntity; 32 import org.thingsboard.server.dao.model.SearchTextEntity;
  33 +import org.thingsboard.server.dao.model.type.JsonCodec;
29 34
  35 +import java.util.HashMap;
  36 +import java.util.Set;
30 import java.util.UUID; 37 import java.util.UUID;
31 38
32 import static org.thingsboard.server.dao.model.ModelConstants.*; 39 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -34,8 +41,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -34,8 +41,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
34 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME) 41 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
35 @EqualsAndHashCode 42 @EqualsAndHashCode
36 @ToString 43 @ToString
  44 +@Slf4j
37 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { 45 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
38 46
  47 + private static final ObjectMapper objectMapper = new ObjectMapper();
  48 +
39 @PartitionKey(value = 0) 49 @PartitionKey(value = 0)
40 @Column(name = ID_PROPERTY) 50 @Column(name = ID_PROPERTY)
41 private UUID id; 51 private UUID id;
@@ -44,16 +54,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -44,16 +54,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
44 @Column(name = DASHBOARD_TENANT_ID_PROPERTY) 54 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
45 private UUID tenantId; 55 private UUID tenantId;
46 56
47 - @PartitionKey(value = 2)  
48 - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)  
49 - private UUID customerId;  
50 -  
51 @Column(name = DASHBOARD_TITLE_PROPERTY) 57 @Column(name = DASHBOARD_TITLE_PROPERTY)
52 private String title; 58 private String title;
53 59
54 @Column(name = SEARCH_TEXT_PROPERTY) 60 @Column(name = SEARCH_TEXT_PROPERTY)
55 private String searchText; 61 private String searchText;
56 62
  63 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)
  64 + private JsonNode assignedCustomers;
  65 +
57 public DashboardInfoEntity() { 66 public DashboardInfoEntity() {
58 super(); 67 super();
59 } 68 }
@@ -65,10 +74,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -65,10 +74,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
65 if (dashboardInfo.getTenantId() != null) { 74 if (dashboardInfo.getTenantId() != null) {
66 this.tenantId = dashboardInfo.getTenantId().getId(); 75 this.tenantId = dashboardInfo.getTenantId().getId();
67 } 76 }
68 - if (dashboardInfo.getCustomerId() != null) {  
69 - this.customerId = dashboardInfo.getCustomerId().getId();  
70 - }  
71 this.title = dashboardInfo.getTitle(); 77 this.title = dashboardInfo.getTitle();
  78 + if (dashboardInfo.getAssignedCustomers() != null) {
  79 + this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers());
  80 + }
72 } 81 }
73 82
74 public UUID getId() { 83 public UUID getId() {
@@ -87,14 +96,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -87,14 +96,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
87 this.tenantId = tenantId; 96 this.tenantId = tenantId;
88 } 97 }
89 98
90 - public UUID getCustomerId() {  
91 - return customerId;  
92 - }  
93 -  
94 - public void setCustomerId(UUID customerId) {  
95 - this.customerId = customerId;  
96 - }  
97 -  
98 public String getTitle() { 99 public String getTitle() {
99 return title; 100 return title;
100 } 101 }
@@ -103,6 +104,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -103,6 +104,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
103 this.title = title; 104 this.title = title;
104 } 105 }
105 106
  107 + public JsonNode getAssignedCustomers() {
  108 + return assignedCustomers;
  109 + }
  110 +
  111 + public void setAssignedCustomers(JsonNode assignedCustomers) {
  112 + this.assignedCustomers = assignedCustomers;
  113 + }
  114 +
106 @Override 115 @Override
107 public String getSearchTextSource() { 116 public String getSearchTextSource() {
108 return getTitle(); 117 return getTitle();
@@ -124,10 +133,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -124,10 +133,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
124 if (tenantId != null) { 133 if (tenantId != null) {
125 dashboardInfo.setTenantId(new TenantId(tenantId)); 134 dashboardInfo.setTenantId(new TenantId(tenantId));
126 } 135 }
127 - if (customerId != null) {  
128 - dashboardInfo.setCustomerId(new CustomerId(customerId));  
129 - }  
130 dashboardInfo.setTitle(title); 136 dashboardInfo.setTitle(title);
  137 + if (assignedCustomers != null) {
  138 + try {
  139 + dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  140 + } catch (JsonProcessingException e) {
  141 + log.warn("Unable to parse assigned customers!", e);
  142 + }
  143 + }
131 return dashboardInfo; 144 return dashboardInfo;
132 } 145 }
133 146
@@ -16,9 +16,12 @@ @@ -16,9 +16,12 @@
16 package org.thingsboard.server.dao.model.sql; 16 package org.thingsboard.server.dao.model.sql;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.databind.JsonNode; 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
20 import lombok.Data; 22 import lombok.Data;
21 import lombok.EqualsAndHashCode; 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
22 import org.hibernate.annotations.Type; 25 import org.hibernate.annotations.Type;
23 import org.hibernate.annotations.TypeDef; 26 import org.hibernate.annotations.TypeDef;
24 import org.thingsboard.server.common.data.Dashboard; 27 import org.thingsboard.server.common.data.Dashboard;
@@ -33,20 +36,23 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -33,20 +36,23 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
33 import javax.persistence.Column; 36 import javax.persistence.Column;
34 import javax.persistence.Entity; 37 import javax.persistence.Entity;
35 import javax.persistence.Table; 38 import javax.persistence.Table;
  39 +import java.util.HashMap;
  40 +import java.util.List;
  41 +import java.util.Set;
36 42
37 @Data 43 @Data
  44 +@Slf4j
38 @EqualsAndHashCode(callSuper = true) 45 @EqualsAndHashCode(callSuper = true)
39 @Entity 46 @Entity
40 @TypeDef(name = "json", typeClass = JsonStringType.class) 47 @TypeDef(name = "json", typeClass = JsonStringType.class)
41 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) 48 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
42 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> { 49 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
43 50
  51 + private static final ObjectMapper objectMapper = new ObjectMapper();
  52 +
44 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) 53 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
45 private String tenantId; 54 private String tenantId;
46 55
47 - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)  
48 - private String customerId;  
49 -  
50 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) 56 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
51 private String title; 57 private String title;
52 58
@@ -54,6 +60,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -54,6 +60,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
54 private String searchText; 60 private String searchText;
55 61
56 @Type(type = "json") 62 @Type(type = "json")
  63 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  64 + private JsonNode assignedCustomers;
  65 +
  66 + @Type(type = "json")
57 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) 67 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
58 private JsonNode configuration; 68 private JsonNode configuration;
59 69
@@ -68,10 +78,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -68,10 +78,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
68 if (dashboard.getTenantId() != null) { 78 if (dashboard.getTenantId() != null) {
69 this.tenantId = toString(dashboard.getTenantId().getId()); 79 this.tenantId = toString(dashboard.getTenantId().getId());
70 } 80 }
71 - if (dashboard.getCustomerId() != null) {  
72 - this.customerId = toString(dashboard.getCustomerId().getId());  
73 - }  
74 this.title = dashboard.getTitle(); 81 this.title = dashboard.getTitle();
  82 + if (dashboard.getAssignedCustomers() != null) {
  83 + this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers());
  84 + }
75 this.configuration = dashboard.getConfiguration(); 85 this.configuration = dashboard.getConfiguration();
76 } 86 }
77 87
@@ -92,10 +102,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -92,10 +102,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
92 if (tenantId != null) { 102 if (tenantId != null) {
93 dashboard.setTenantId(new TenantId(toUUID(tenantId))); 103 dashboard.setTenantId(new TenantId(toUUID(tenantId)));
94 } 104 }
95 - if (customerId != null) {  
96 - dashboard.setCustomerId(new CustomerId(toUUID(customerId)));  
97 - }  
98 dashboard.setTitle(title); 105 dashboard.setTitle(title);
  106 + if (assignedCustomers != null) {
  107 + try {
  108 + dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  109 + } catch (JsonProcessingException e) {
  110 + log.warn("Unable to parse assigned customers!", e);
  111 + }
  112 + }
99 dashboard.setConfiguration(configuration); 113 dashboard.setConfiguration(configuration);
100 return dashboard; 114 return dashboard;
101 } 115 }
@@ -16,8 +16,13 @@ @@ -16,8 +16,13 @@
16 package org.thingsboard.server.dao.model.sql; 16 package org.thingsboard.server.dao.model.sql;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
19 import lombok.Data; 22 import lombok.Data;
20 import lombok.EqualsAndHashCode; 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.hibernate.annotations.Type;
21 import org.thingsboard.server.common.data.DashboardInfo; 26 import org.thingsboard.server.common.data.DashboardInfo;
22 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
23 import org.thingsboard.server.common.data.id.DashboardId; 28 import org.thingsboard.server.common.data.id.DashboardId;
@@ -29,25 +34,31 @@ import org.thingsboard.server.dao.model.SearchTextEntity; @@ -29,25 +34,31 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
29 import javax.persistence.Column; 34 import javax.persistence.Column;
30 import javax.persistence.Entity; 35 import javax.persistence.Entity;
31 import javax.persistence.Table; 36 import javax.persistence.Table;
  37 +import java.util.HashMap;
  38 +import java.util.Set;
32 39
33 @Data 40 @Data
  41 +@Slf4j
34 @EqualsAndHashCode(callSuper = true) 42 @EqualsAndHashCode(callSuper = true)
35 @Entity 43 @Entity
36 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) 44 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
37 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> { 45 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
38 46
  47 + private static final ObjectMapper objectMapper = new ObjectMapper();
  48 +
39 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) 49 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
40 private String tenantId; 50 private String tenantId;
41 51
42 - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)  
43 - private String customerId;  
44 -  
45 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) 52 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
46 private String title; 53 private String title;
47 54
48 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) 55 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
49 private String searchText; 56 private String searchText;
50 57
  58 + @Type(type = "json")
  59 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  60 + private JsonNode assignedCustomers;
  61 +
51 public DashboardInfoEntity() { 62 public DashboardInfoEntity() {
52 super(); 63 super();
53 } 64 }
@@ -59,10 +70,10 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -59,10 +70,10 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
59 if (dashboardInfo.getTenantId() != null) { 70 if (dashboardInfo.getTenantId() != null) {
60 this.tenantId = toString(dashboardInfo.getTenantId().getId()); 71 this.tenantId = toString(dashboardInfo.getTenantId().getId());
61 } 72 }
62 - if (dashboardInfo.getCustomerId() != null) {  
63 - this.customerId = toString(dashboardInfo.getCustomerId().getId());  
64 - }  
65 this.title = dashboardInfo.getTitle(); 73 this.title = dashboardInfo.getTitle();
  74 + if (dashboardInfo.getAssignedCustomers() != null) {
  75 + this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers());
  76 + }
66 } 77 }
67 78
68 @Override 79 @Override
@@ -86,10 +97,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -86,10 +97,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
86 if (tenantId != null) { 97 if (tenantId != null) {
87 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId))); 98 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
88 } 99 }
89 - if (customerId != null) {  
90 - dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));  
91 - }  
92 dashboardInfo.setTitle(title); 100 dashboardInfo.setTitle(title);
  101 + if (assignedCustomers != null) {
  102 + try {
  103 + dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  104 + } catch (JsonProcessingException e) {
  105 + log.warn("Unable to parse assigned customers!", e);
  106 + }
  107 + }
93 return dashboardInfo; 108 return dashboardInfo;
94 } 109 }
95 110
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import org.thingsboard.server.common.data.id.IdBased;
  19 +import org.thingsboard.server.common.data.page.TimePageLink;
  20 +
  21 +import java.sql.Time;
  22 +import java.util.List;
  23 +import java.util.UUID;
  24 +
  25 +public abstract class TimePaginatedRemover<I, D extends IdBased<?>> {
  26 +
  27 + private static final int DEFAULT_LIMIT = 100;
  28 +
  29 + public void removeEntities(I id) {
  30 + TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT);
  31 + boolean hasNext = true;
  32 + while (hasNext) {
  33 + List<D> entities = findEntities(id, pageLink);
  34 + for (D entity : entities) {
  35 + removeEntity(entity);
  36 + }
  37 + hasNext = entities.size() == pageLink.getLimit();
  38 + if (hasNext) {
  39 + int index = entities.size() - 1;
  40 + UUID idOffset = entities.get(index).getUuidId();
  41 + pageLink.setIdOffset(idOffset);
  42 + }
  43 + }
  44 + }
  45 +
  46 + protected abstract List<D> findEntities(I id, TimePageLink pageLink);
  47 +
  48 + protected abstract void removeEntity(D entity);
  49 +
  50 +}
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
17 17
18 import org.thingsboard.server.common.data.id.EntityId; 18 import org.thingsboard.server.common.data.id.EntityId;
19 import org.thingsboard.server.common.data.id.UUIDBased; 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.page.BasePageLink;
20 import org.thingsboard.server.common.data.page.TextPageLink; 21 import org.thingsboard.server.common.data.page.TextPageLink;
21 import org.thingsboard.server.dao.exception.IncorrectParameterException; 22 import org.thingsboard.server.dao.exception.IncorrectParameterException;
22 23
@@ -116,7 +117,7 @@ public class Validator { @@ -116,7 +117,7 @@ public class Validator {
116 * @param pageLink the page link 117 * @param pageLink the page link
117 * @param errorMessage the error message for exception 118 * @param errorMessage the error message for exception
118 */ 119 */
119 - public static void validatePageLink(TextPageLink pageLink, String errorMessage) { 120 + public static void validatePageLink(BasePageLink pageLink, String errorMessage) {
120 if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) { 121 if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) {
121 throw new IncorrectParameterException(errorMessage); 122 throw new IncorrectParameterException(errorMessage);
122 } 123 }
@@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt @@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt
39 @Param("idOffset") String idOffset, 39 @Param("idOffset") String idOffset,
40 Pageable pageable); 40 Pageable pageable);
41 41
42 - @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +  
43 - "AND di.customerId = :customerId AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +  
44 - "AND di.id > :idOffset ORDER BY di.id")  
45 - List<DashboardInfoEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,  
46 - @Param("customerId") String customerId,  
47 - @Param("searchText") String searchText,  
48 - @Param("idOffset") String idOffset,  
49 - Pageable pageable);  
50 } 42 }
@@ -15,19 +15,31 @@ @@ -15,19 +15,31 @@
15 */ 15 */
16 package org.thingsboard.server.dao.sql.dashboard; 16 package org.thingsboard.server.dao.sql.dashboard;
17 17
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.extern.slf4j.Slf4j;
18 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.data.domain.PageRequest; 23 import org.springframework.data.domain.PageRequest;
20 import org.springframework.data.repository.CrudRepository; 24 import org.springframework.data.repository.CrudRepository;
21 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
22 import org.thingsboard.server.common.data.DashboardInfo; 26 import org.thingsboard.server.common.data.DashboardInfo;
  27 +import org.thingsboard.server.common.data.EntityType;
23 import org.thingsboard.server.common.data.UUIDConverter; 28 import org.thingsboard.server.common.data.UUIDConverter;
  29 +import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.page.TextPageLink; 30 import org.thingsboard.server.common.data.page.TextPageLink;
  31 +import org.thingsboard.server.common.data.page.TimePageLink;
  32 +import org.thingsboard.server.common.data.relation.EntityRelation;
  33 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
25 import org.thingsboard.server.dao.DaoUtil; 34 import org.thingsboard.server.dao.DaoUtil;
26 import org.thingsboard.server.dao.dashboard.DashboardInfoDao; 35 import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
27 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity; 36 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
  37 +import org.thingsboard.server.dao.relation.RelationDao;
28 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; 38 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
29 import org.thingsboard.server.dao.util.SqlDao; 39 import org.thingsboard.server.dao.util.SqlDao;
30 40
  41 +import java.sql.Time;
  42 +import java.util.ArrayList;
31 import java.util.List; 43 import java.util.List;
32 import java.util.Objects; 44 import java.util.Objects;
33 import java.util.UUID; 45 import java.util.UUID;
@@ -37,11 +49,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; @@ -37,11 +49,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
37 /** 49 /**
38 * Created by Valerii Sosliuk on 5/6/2017. 50 * Created by Valerii Sosliuk on 5/6/2017.
39 */ 51 */
  52 +@Slf4j
40 @Component 53 @Component
41 @SqlDao 54 @SqlDao
42 public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao { 55 public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
43 56
44 @Autowired 57 @Autowired
  58 + private RelationDao relationDao;
  59 +
  60 + @Autowired
45 private DashboardInfoRepository dashboardInfoRepository; 61 private DashboardInfoRepository dashboardInfoRepository;
46 62
47 @Override 63 @Override
@@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE @@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
65 } 81 }
66 82
67 @Override 83 @Override
68 - public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {  
69 - return DaoUtil.convertDataList(dashboardInfoRepository  
70 - .findByTenantIdAndCustomerId(  
71 - UUIDConverter.fromTimeUUID(tenantId),  
72 - UUIDConverter.fromTimeUUID(customerId),  
73 - Objects.toString(pageLink.getTextSearch(), ""),  
74 - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),  
75 - new PageRequest(0, pageLink.getLimit()))); 84 + public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
  85 + log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
  86 +
  87 + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
  88 +
  89 + return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
  90 + List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
  91 + for (EntityRelation relation : input) {
  92 + dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
  93 + }
  94 + return Futures.successfulAsList(dashboardFutures);
  95 + });
76 } 96 }
77 } 97 }
@@ -364,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali @@ -364,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali
364 CREATE TABLE IF NOT EXISTS thingsboard.dashboard ( 364 CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
365 id timeuuid, 365 id timeuuid,
366 tenant_id timeuuid, 366 tenant_id timeuuid,
367 - customer_id timeuuid,  
368 title text, 367 title text,
369 search_text text, 368 search_text text,
  369 + assigned_customers text,
370 configuration text, 370 configuration text,
371 - PRIMARY KEY (id, tenant_id, customer_id) 371 + PRIMARY KEY (id, tenant_id)
372 ); 372 );
373 373
374 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS 374 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
375 SELECT * 375 SELECT *
376 from thingsboard.dashboard 376 from thingsboard.dashboard
377 - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL  
378 - PRIMARY KEY ( tenant_id, search_text, id, customer_id )  
379 - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC );  
380 -  
381 -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS  
382 - SELECT *  
383 - from thingsboard.dashboard  
384 - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL  
385 - PRIMARY KEY ( customer_id, tenant_id, search_text, id )  
386 - WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); 377 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  378 + PRIMARY KEY ( tenant_id, search_text, id )
  379 + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
387 380
388 CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( 381 CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
389 entity_type text, // (DEVICE, CUSTOMER, TENANT) 382 entity_type text, // (DEVICE, CUSTOMER, TENANT)
@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer ( @@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer (
105 CREATE TABLE IF NOT EXISTS dashboard ( 105 CREATE TABLE IF NOT EXISTS dashboard (
106 id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, 106 id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
107 configuration varchar(10000000), 107 configuration varchar(10000000),
108 - customer_id varchar(31), 108 + assigned_customers varchar(1000000),
109 search_text varchar(255), 109 search_text varchar(255),
110 tenant_id varchar(31), 110 tenant_id varchar(31),
111 title varchar(255) 111 title varchar(255)
@@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.page.TextPageData; 30 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 31 import org.thingsboard.server.common.data.page.TextPageLink;
  32 +import org.thingsboard.server.common.data.page.TimePageData;
  33 +import org.thingsboard.server.common.data.page.TimePageLink;
32 import org.thingsboard.server.dao.exception.DataValidationException; 34 import org.thingsboard.server.dao.exception.DataValidationException;
33 import org.thingsboard.server.dao.model.ModelConstants; 35 import org.thingsboard.server.dao.model.ModelConstants;
34 36
35 import java.io.IOException; 37 import java.io.IOException;
  38 +import java.sql.Time;
36 import java.util.ArrayList; 39 import java.util.ArrayList;
37 import java.util.Collections; 40 import java.util.Collections;
38 import java.util.List; 41 import java.util.List;
  42 +import java.util.concurrent.ExecutionException;
39 43
40 public abstract class BaseDashboardServiceTest extends AbstractServiceTest { 44 public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
41 45
@@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
68 Assert.assertNotNull(savedDashboard.getId()); 72 Assert.assertNotNull(savedDashboard.getId());
69 Assert.assertTrue(savedDashboard.getCreatedTime() > 0); 73 Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
70 Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId()); 74 Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
71 - Assert.assertNotNull(savedDashboard.getCustomerId());  
72 - Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());  
73 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); 75 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
74 76
75 savedDashboard.setTitle("My new dashboard"); 77 savedDashboard.setTitle("My new dashboard");
@@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
280 } 282 }
281 283
282 @Test 284 @Test
283 - public void testFindDashboardsByTenantIdAndCustomerId() { 285 + public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
284 Tenant tenant = new Tenant(); 286 Tenant tenant = new Tenant();
285 tenant.setTitle("Test tenant"); 287 tenant.setTitle("Test tenant");
286 tenant = tenantService.saveTenant(tenant); 288 tenant = tenantService.saveTenant(tenant);
@@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
303 } 305 }
304 306
305 List<DashboardInfo> loadedDashboards = new ArrayList<>(); 307 List<DashboardInfo> loadedDashboards = new ArrayList<>();
306 - TextPageLink pageLink = new TextPageLink(23);  
307 - TextPageData<DashboardInfo> pageData = null; 308 + TimePageLink pageLink = new TimePageLink(23);
  309 + TimePageData<DashboardInfo> pageData = null;
308 do { 310 do {
309 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); 311 + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
310 loadedDashboards.addAll(pageData.getData()); 312 loadedDashboards.addAll(pageData.getData());
311 if (pageData.hasNext()) { 313 if (pageData.hasNext()) {
312 pageLink = pageData.getNextPageLink(); 314 pageLink = pageData.getNextPageLink();
@@ -320,96 +322,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -320,96 +322,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
320 322
321 dashboardService.unassignCustomerDashboards(tenantId, customerId); 323 dashboardService.unassignCustomerDashboards(tenantId, customerId);
322 324
323 - pageLink = new TextPageLink(42);  
324 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); 325 + pageLink = new TimePageLink(42);
  326 + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
325 Assert.assertFalse(pageData.hasNext()); 327 Assert.assertFalse(pageData.hasNext());
326 Assert.assertTrue(pageData.getData().isEmpty()); 328 Assert.assertTrue(pageData.getData().isEmpty());
327 329
328 tenantService.deleteTenant(tenantId); 330 tenantService.deleteTenant(tenantId);
329 } 331 }
330 -  
331 - @Test  
332 - public void testFindDashboardsByTenantIdCustomerIdAndTitle() {  
333 -  
334 - Customer customer = new Customer();  
335 - customer.setTitle("Test customer");  
336 - customer.setTenantId(tenantId);  
337 - customer = customerService.saveCustomer(customer);  
338 - CustomerId customerId = customer.getId();  
339 -  
340 - String title1 = "Dashboard title 1";  
341 - List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();  
342 - for (int i=0;i<124;i++) {  
343 - Dashboard dashboard = new Dashboard();  
344 - dashboard.setTenantId(tenantId);  
345 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));  
346 - String title = title1+suffix;  
347 - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();  
348 - dashboard.setTitle(title);  
349 - dashboard = dashboardService.saveDashboard(dashboard);  
350 - dashboardsTitle1.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));  
351 - }  
352 - String title2 = "Dashboard title 2";  
353 - List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();  
354 - for (int i=0;i<151;i++) {  
355 - Dashboard dashboard = new Dashboard();  
356 - dashboard.setTenantId(tenantId);  
357 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));  
358 - String title = title2+suffix;  
359 - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();  
360 - dashboard.setTitle(title);  
361 - dashboard = dashboardService.saveDashboard(dashboard);  
362 - dashboardsTitle2.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));  
363 - }  
364 -  
365 - List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();  
366 - TextPageLink pageLink = new TextPageLink(24, title1);  
367 - TextPageData<DashboardInfo> pageData = null;  
368 - do {  
369 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);  
370 - loadedDashboardsTitle1.addAll(pageData.getData());  
371 - if (pageData.hasNext()) {  
372 - pageLink = pageData.getNextPageLink();  
373 - }  
374 - } while (pageData.hasNext());  
375 -  
376 - Collections.sort(dashboardsTitle1, idComparator);  
377 - Collections.sort(loadedDashboardsTitle1, idComparator);  
378 -  
379 - Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);  
380 -  
381 - List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();  
382 - pageLink = new TextPageLink(4, title2);  
383 - do {  
384 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);  
385 - loadedDashboardsTitle2.addAll(pageData.getData());  
386 - if (pageData.hasNext()) {  
387 - pageLink = pageData.getNextPageLink();  
388 - }  
389 - } while (pageData.hasNext());  
390 332
391 - Collections.sort(dashboardsTitle2, idComparator);  
392 - Collections.sort(loadedDashboardsTitle2, idComparator);  
393 -  
394 - Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);  
395 -  
396 - for (DashboardInfo dashboard : loadedDashboardsTitle1) {  
397 - dashboardService.deleteDashboard(dashboard.getId());  
398 - }  
399 -  
400 - pageLink = new TextPageLink(4, title1);  
401 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);  
402 - Assert.assertFalse(pageData.hasNext());  
403 - Assert.assertEquals(0, pageData.getData().size());  
404 -  
405 - for (DashboardInfo dashboard : loadedDashboardsTitle2) {  
406 - dashboardService.deleteDashboard(dashboard.getId());  
407 - }  
408 -  
409 - pageLink = new TextPageLink(4, title2);  
410 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);  
411 - Assert.assertFalse(pageData.hasNext());  
412 - Assert.assertEquals(0, pageData.getData().size());  
413 - customerService.deleteCustomer(customerId);  
414 - }  
415 } 333 }
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.dao.sql.dashboard; 16 package org.thingsboard.server.dao.sql.dashboard;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import org.junit.Assert;
19 import org.junit.Test; 20 import org.junit.Test;
20 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.beans.factory.annotation.Autowired;
21 import org.thingsboard.server.common.data.DashboardInfo; 22 import org.thingsboard.server.common.data.DashboardInfo;
@@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest { @@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
40 @Test 41 @Test
41 public void testFindDashboardsByTenantId() { 42 public void testFindDashboardsByTenantId() {
42 UUID tenantId1 = UUIDs.timeBased(); 43 UUID tenantId1 = UUIDs.timeBased();
43 - UUID customerId1 = UUIDs.timeBased();  
44 UUID tenantId2 = UUIDs.timeBased(); 44 UUID tenantId2 = UUIDs.timeBased();
45 - UUID customerId2 = UUIDs.timeBased();  
46 45
47 for (int i = 0; i < 20; i++) { 46 for (int i = 0; i < 20; i++) {
48 - createDashboard(tenantId1, customerId1, i);  
49 - createDashboard(tenantId2, customerId2, i * 2); 47 + createDashboard(tenantId1, i);
  48 + createDashboard(tenantId2, i * 2);
50 } 49 }
51 50
52 TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD"); 51 TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
53 List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1); 52 List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1);
54 - assertEquals(15, dashboardInfos1.size()); 53 + Assert.assertEquals(15, dashboardInfos1.size());
55 54
56 TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null); 55 TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
57 List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2); 56 List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2);
58 - assertEquals(5, dashboardInfos2.size()); 57 + Assert.assertEquals(5, dashboardInfos2.size());
59 } 58 }
60 59
61 - @Test  
62 - public void testFindDashboardsByTenantAndCustomerId() {  
63 - UUID tenantId1 = UUIDs.timeBased();  
64 - UUID customerId1 = UUIDs.timeBased();  
65 - UUID tenantId2 = UUIDs.timeBased();  
66 - UUID customerId2 = UUIDs.timeBased();  
67 -  
68 - for (int i = 0; i < 20; i++) {  
69 - createDashboard(tenantId1, customerId1, i);  
70 - createDashboard(tenantId2, customerId2, i * 2);  
71 - }  
72 -  
73 - TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");  
74 - List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1);  
75 - assertEquals(15, dashboardInfos1.size());  
76 -  
77 - TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);  
78 - List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink2);  
79 - assertEquals(5, dashboardInfos2.size());  
80 - }  
81 -  
82 - private void assertEquals(int i, int size) {  
83 - }  
84 -  
85 - private void createDashboard(UUID tenantId, UUID customerId, int index) { 60 + private void createDashboard(UUID tenantId, int index) {
86 DashboardInfo dashboardInfo = new DashboardInfo(); 61 DashboardInfo dashboardInfo = new DashboardInfo();
87 dashboardInfo.setId(new DashboardId(UUIDs.timeBased())); 62 dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
88 dashboardInfo.setTenantId(new TenantId(tenantId)); 63 dashboardInfo.setTenantId(new TenantId(tenantId));
89 - dashboardInfo.setCustomerId(new CustomerId(customerId));  
90 dashboardInfo.setTitle("DASHBOARD_" + index); 64 dashboardInfo.setTitle("DASHBOARD_" + index);
91 dashboardInfoDao.save(dashboardInfo); 65 dashboardInfoDao.save(dashboardInfo);
92 } 66 }