diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.cql b/application/src/main/data/upgrade/2.1.1/schema_update.cql index e0cff4108f..888f43c5ad 100644 --- a/application/src/main/data/upgrade/2.1.1/schema_update.cql +++ b/application/src/main/data/upgrade/2.1.1/schema_update.cql @@ -39,55 +39,54 @@ CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_name AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL - AND tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND keys IS NOT NULL - AND start_ts IS NOT NULL - AND end_ts IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, name, id, entity_id, customer_id) - WITH CLUSTERING ORDER BY (name ASC, id DESC, entity_id DESC, customer_id DESC); + WHERE tenant_id IS NOT NULL + AND entity_id IS NOT NULL + AND customer_id IS NOT NULL + AND name IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id) + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_entity AS +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL - AND tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND keys IS NOT NULL - AND start_ts IS NOT NULL - AND end_ts IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, entity_id, id, customer_id, name) - WITH CLUSTERING ORDER BY (entity_id ASC, customer_id ASC, id DESC, name DESC); + WHERE tenant_id IS NOT NULL + AND entity_id IS NOT NULL + AND customer_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id) + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); + +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity AS + SELECT * + from thingsboard.entity_views + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND entity_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, entity_id, search_text, id, customer_id) + WITH CLUSTERING ORDER BY (entity_id ASC, search_text ASC, id DESC, customer_id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_customer AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL - AND tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND keys IS NOT NULL - AND start_ts IS NOT NULL - AND end_ts IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, id, entity_id, name) - WITH CLUSTERING ORDER BY (customer_id ASC, id DESC, entity_id DESC, name DESC); + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND entity_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id) + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_customer_and_entity AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL - AND tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND keys IS NOT NULL - AND start_ts IS NOT NULL - AND end_ts IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, entity_id, id, name) - WITH CLUSTERING ORDER BY (customer_id ASC, entity_id DESC, id DESC, name DESC); + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND entity_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, customer_id, entity_id, search_text, id) + WITH CLUSTERING ORDER BY (customer_id DESC, entity_id ASC, search_text ASC, id DESC); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index b5adc327f5..26a48daff6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -15,32 +15,28 @@ */ package org.thingsboard.server.controller; -import com.google.common.util.concurrent.ListenableFuture; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.service.security.model.SecurityUser; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; + /** * Created by Victor Basanets on 8/28/2017. */ @@ -109,9 +105,9 @@ public class EntityViewController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) @ResponseBody - public EntityView assignEntityViewToCustomer(@PathVariable("customerId") String strCustomerId, + public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId, @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { - checkParameter("customerId", strCustomerId); + checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index be5149dcb8..362cc29a9b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -298,6 +298,9 @@ caffeine: assets: timeToLiveInMinutes: 1440 maxSize: 100000 + entityViews: + timeToLiveInMinutes: 1440 + maxSize: 100000 redis: # standalone or cluster diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index c7ac98d413..5c62e6ad13 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -15,25 +15,31 @@ */ package org.thingsboard.server.controller; +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.*; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.objects.TelemetryEntityView; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.model.ModelConstants; -import java.util.Arrays; +import java.util.*; +import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; public abstract class BaseEntityViewControllerTest extends AbstractControllerTest { + private IdComparator idComparator; private Tenant savedTenant; private User tenantAdmin; private Device testDevice; @@ -43,10 +49,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes public void beforeTest() throws Exception { loginSysAdmin(); - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); + idComparator = new IdComparator<>(); + savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class); Assert.assertNotNull(savedTenant); tenantAdmin = new User(); @@ -62,13 +67,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes device.setName("Test device"); device.setType("default"); testDevice = doPost("/api/device", device, Device.class); - obj = new TelemetryEntityView( Arrays.asList("109L", "209L"), new AttributesEntityView( - Arrays.asList("caKey1", "caKey2", "caKey3"), - Arrays.asList("saKey1", "saKey2", "saKey3", "saKey4"), - Arrays.asList("shKey1", "shKey2", "shKey3", "shKey4", "shKey5"))); + Arrays.asList("caKey1", "caKey2"), + Arrays.asList("saKey1", "saKey2", "saKey3"), + Arrays.asList("shKey1", "shKey2", "shKey3", "shKey4") + ) + ); } @After @@ -81,24 +87,15 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testFindEntityViewById() throws Exception { - EntityView view = new EntityView(); - view.setName("Test entity view"); - view.setEntityId(testDevice.getId()); - view.setKeys(new TelemetryEntityView(obj)); - EntityView savedView = doPost("/api/entity-view", view, EntityView.class); - EntityView foundView = doGet("/api/entity-view/" + savedView.getId().getId().toString(), EntityView.class); + EntityView savedView = doPost("/api/entityView", getNewEntityView("Test entity view"), EntityView.class); + EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); Assert.assertNotNull(foundView); Assert.assertEquals(savedView, foundView); } @Test - public void testSaveEntityViewWithIdOfDevice() throws Exception { - EntityView view = new EntityView(); - view.setEntityId(testDevice.getId()); - view.setName("Test entity view"); - view.setTenantId(savedTenant.getId()); - view.setKeys(new TelemetryEntityView(obj)); - EntityView savedView = doPost("/api/entity-view", view, EntityView.class); + public void testSaveEntityView() throws Exception { + EntityView savedView = doPost("/api/entityView", getNewEntityView("Test entity view"), EntityView.class); Assert.assertNotNull(savedView); Assert.assertNotNull(savedView.getId()); @@ -109,26 +106,253 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Assert.assertEquals(savedView.getName(), savedView.getName()); savedView.setName("New test entity view"); - doPost("/api/entity-view", savedView, EntityView.class); - - EntityView foundEntityView = doGet("/api/entity-view/" - + savedView.getId().getId().toString(), EntityView.class); + doPost("/api/entityView", savedView, EntityView.class); + EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); Assert.assertEquals(foundEntityView.getName(), savedView.getName()); } @Test public void testDeleteEntityView() throws Exception { - EntityView view = new EntityView(); - view.setName("Test entity view"); - view.setEntityId(testDevice.getId()); - view.setKeys(new TelemetryEntityView((TelemetryEntityView) obj)); - EntityView savedView = doPost("/api/entity-view", view, EntityView.class); + EntityView view = getNewEntityView("Test entity view"); + Customer customer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class); + view.setCustomerId(customer.getId()); + EntityView savedView = doPost("/api/entityView", view, EntityView.class); - doDelete("/api/entity-view/" + savedView.getId().getId().toString()) + doDelete("/api/entityView/" + savedView.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/entity-view/" + savedView.getId().getId().toString()) + doGet("/api/entityView/" + savedView.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveEntityViewWithEmptyName() throws Exception { + doPost("/api/entityView", new EntityView()) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Entity view name should be specified!"))); + } + + @Test + public void testAssignAndUnAssignedEntityViewToCustomer() throws Exception { + EntityView view = getNewEntityView("Test entity view"); + Customer savedCustomer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class); + view.setCustomerId(savedCustomer.getId()); + EntityView savedView = doPost("/api/entityView", view, EntityView.class); + + EntityView assignedView = doPost( + "/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString(), + EntityView.class); + Assert.assertEquals(savedCustomer.getId(), assignedView.getCustomerId()); + + EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); + Assert.assertEquals(savedCustomer.getId(), foundView.getCustomerId()); + + EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class); + Assert.assertEquals(ModelConstants.NULL_UUID, unAssignedView.getCustomerId().getId()); + + foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); + Assert.assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); + } + + @Test + public void testAssignEntityViewToNonExistentCustomer() throws Exception { + EntityView savedView = doPost("/api/entityView", getNewEntityView("Test entity view"), EntityView.class); + doPost("/api/customer/" + UUIDs.timeBased().toString() + "/device/" + savedView.getId().getId().toString()) .andExpect(status().isNotFound()); } + + @Test + public void testAssignEntityViewToCustomerFromDifferentTenant() throws Exception { + loginSysAdmin(); + + Tenant tenant2 = getNewTenant("Different tenant"); + Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); + Assert.assertNotNull(savedTenant2); + + User tenantAdmin2 = new User(); + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin2.setTenantId(savedTenant2.getId()); + tenantAdmin2.setEmail("tenant3@thingsboard.org"); + tenantAdmin2.setFirstName("Joe"); + tenantAdmin2.setLastName("Downs"); + createUserAndLogin(tenantAdmin2, "testPassword1"); + + Customer customer = getNewCustomer("Different customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + login(tenantAdmin.getEmail(), "testPassword1"); + + EntityView view = getNewEntityView("Test entity view"); + EntityView savedView = doPost("/api/entityView", view, EntityView.class); + + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString()) + .andExpect(status().isForbidden()); + + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testGetCustomerEntityViews() throws Exception { + CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId(); + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; + + List views = new ArrayList<>(); + for (int i = 0; i < 128; i++) { + views.add(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" + + getNewEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class)); + } + + List loadedViews = loadListOf(new TextPageLink(23), urlTemplate); + + Collections.sort(views, idComparator); + Collections.sort(loadedViews, idComparator); + + Assert.assertEquals(views, loadedViews); + } + + @Test + public void testGetCustomerEntityViewsByName() throws Exception { + CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId(); + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; + + String name1 = "Entity view name1"; + List namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString() + + "/entityView/"); + List loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate); + Collections.sort(namesOfView1, idComparator); + Collections.sort(loadedNamesOfView1, idComparator); + Assert.assertEquals(namesOfView1, loadedNamesOfView1); + + String name2 = "Entity view name2"; + List NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString() + + "/entityView/"); + List loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate); + Collections.sort(NamesOfView2, idComparator); + Collections.sort(loadedNamesOfView2, idComparator); + Assert.assertEquals(NamesOfView2, loadedNamesOfView2); + + for (EntityView view : loadedNamesOfView1) { + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + } + TextPageData pageData = doGetTypedWithPageLink(urlTemplate, + new TypeReference>(){}, new TextPageLink(4, name1)); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (EntityView view : loadedNamesOfView2) { + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + } + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>(){}, + new TextPageLink(4, name2)); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } + + @Test + public void testGetTenantEntityViews() throws Exception { + + List views = new ArrayList<>(); + for (int i = 0; i < 178; i++) { + views.add(doPost("/api/entityView/", getNewEntityView("Test entity view" + i), EntityView.class)); + } + List loadedViews = loadListOf(new TextPageLink(23), "/api/tenant/entityViews?"); + + Collections.sort(views, idComparator); + Collections.sort(loadedViews, idComparator); + + Assert.assertEquals(views, loadedViews); + } + + @Test + public void testGetTenantEntityViewsByName() throws Exception { + String name1 = "Entity view name1"; + List namesOfView1 = fillListOf(143, name1); + List loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), "/api/tenant/entityViews?"); + Collections.sort(namesOfView1, idComparator); + Collections.sort(loadedNamesOfView1, idComparator); + Assert.assertEquals(namesOfView1, loadedNamesOfView1); + + String name2 = "Entity view name2"; + List NamesOfView2 = fillListOf(75, name2); + List loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), "/api/tenant/entityViews?"); + Collections.sort(NamesOfView2, idComparator); + Collections.sort(loadedNamesOfView2, idComparator); + Assert.assertEquals(NamesOfView2, loadedNamesOfView2); + + for (EntityView view : loadedNamesOfView1) { + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + } + TextPageData pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", + new TypeReference>(){}, new TextPageLink(4, name1)); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (EntityView view : loadedNamesOfView2) { + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + } + pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference>(){}, + new TextPageLink(4, name2)); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } + + private EntityView getNewEntityView(String name) throws Exception { + EntityView view = new EntityView(); + view.setEntityId(testDevice.getId()); + view.setTenantId(savedTenant.getId()); + view.setName(name); + view.setKeys(new TelemetryEntityView(obj)); + return doPost("/api/entityView", view, EntityView.class); + } + + private Customer getNewCustomer(String title) { + Customer customer = new Customer(); + customer.setTitle(title); + return customer; + } + + private Tenant getNewTenant(String title) { + Tenant tenant = new Tenant(); + tenant.setTitle(title); + return tenant; + } + + private List fillListOf(int limit, String partOfName, String urlTemplate) throws Exception { + List views = new ArrayList<>(); + for (EntityView view : fillListOf(limit, partOfName)) { + views.add(doPost(urlTemplate + view.getId().getId().toString(), EntityView.class)); + } + return views; + } + + private List fillListOf(int limit, String partOfName) throws Exception { + List viewNames = new ArrayList<>(); + for (int i = 0; i < limit; i++) { + String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15); + fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase(); + EntityView view = getNewEntityView(fullName); + Customer customer = getNewCustomer("Test customer " + String.valueOf(Math.random())); + view.setCustomerId(doPost("/api/customer", customer, Customer.class).getId()); + viewNames.add(doPost("/api/entityView", view, EntityView.class)); + } + return viewNames; + } + + private List loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception { + List loadedItems = new ArrayList<>(); + TextPageData pageData; + do { + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>(){}, pageLink); + loadedItems.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + return loadedItems; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java index 5de34e4b71..98f6105646 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java @@ -67,49 +67,75 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find entity-views by tenantId [{}] and pageLink [{}]", tenantId, pageLink); + log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink); List entityViewEntities = findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Collections.singletonList(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)), pageLink); - - log.trace("Found entity-views [{}] by tenantId [{}] and pageLink [{}]", entityViewEntities, tenantId, pageLink); + Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink); + log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]", + entityViewEntities, tenantId, pageLink); return DaoUtil.convertDataList(entityViewEntities); } @Override - public Optional findEntityViewByTenantIdAndName(UUID tenantId, String entityViewName) { - return Optional.ofNullable(DaoUtil.getData( - findOneByStatement(select().from(ENTITY_VIEW_TENANT_AND_NAME_VIEW_NAME).where() - .and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)) - .and(eq(ENTITY_VIEW_NAME_PROPERTY, entityViewName)))) - ); + public Optional findEntityViewByTenantIdAndName(UUID tenantId, String name) { + Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where(); + query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)); + query.and(eq(ENTITY_VIEW_NAME_PROPERTY, name)); + return Optional.ofNullable(DaoUtil.getData(findOneByStatement(query))); } @Override public List findEntityViewByTenantIdAndEntityId(UUID tenantId, UUID entityId, TextPageLink pageLink) { - log.debug("Try to find entity-views by tenantId [{}], entityId[{}] and pageLink [{}]", tenantId, entityId, pageLink); - List entityViewEntities = findPageWithTextSearch(DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(DEVICE_CUSTOMER_ID_PROPERTY, entityId), - eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), + log.debug("Try to find entity views by tenantId [{}], entityId [{}] and pageLink [{}]", + tenantId, entityId, pageLink); + List entityViewEntities = findPageWithTextSearch( + ENTITY_VIEW_BY_TENANT_AND_ENTITY_AND_SEARCH_TEXT, + Arrays.asList(eq(CUSTOMER_ID_PROPERTY, entityId), eq(TENANT_ID_PROPERTY, tenantId)), pageLink); - - log.trace("Found entity-views [{}] by tenantId [{}], entityId [{}] and pageLink [{}]", entityViewEntities, tenantId, entityId, pageLink); + log.trace("Found entity views [{}] by tenantId [{}], entityId [{}] and pageLink [{}]", + entityViewEntities, tenantId, entityId, pageLink); return DaoUtil.convertDataList(entityViewEntities); } @Override public List findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { - return null; + log.debug("Try to find entity views by tenantId [{}], customerId[{}] and pageLink [{}]", + tenantId, customerId, pageLink); + List entityViewEntities = findPageWithTextSearch( + ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_SEARCH_TEXT, + Arrays.asList(eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)), + pageLink); + log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", + entityViewEntities, tenantId, customerId, pageLink); + return DaoUtil.convertDataList(entityViewEntities); } @Override - public List findEntityViewsByTenantIdAndCustomerIdAndEntityId(UUID tenantId, UUID customerId, UUID entityId, TextPageLink pageLink) { - return null; + public List findEntityViewsByTenantIdAndCustomerIdAndEntityId(UUID tenantId, + UUID customerId, + UUID entityId, + TextPageLink pageLink) { + + log.debug("Try to find entity views by tenantId [{}], customerId [{}], entityId [{}] and pageLink [{}]", + tenantId, customerId, entityId, pageLink); + List entityViewEntities = findPageWithTextSearch( + ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_ENTITY_AND_SEARCH_TEXT, + Arrays.asList( + eq(TENANT_ID_PROPERTY, tenantId), + eq(CUSTOMER_ID_PROPERTY, customerId), + eq(ENTITY_ID_COLUMN, entityId)), + pageLink); + log.trace("Found devices [{}] by tenantId [{}], customerId [{}], entityId [{}] and pageLink [{}]", + entityViewEntities, tenantId, customerId, entityId, pageLink); + return DaoUtil.convertDataList(entityViewEntities); } @Override public ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { - // TODO: implement this - return null; + log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId); + Select.Where query = select().from(getColumnFamilyName()).where(); + query.and(eq(TENANT_ID_PROPERTY, tenantId)); + query.and(eq(ENTITY_ID_COLUMN, entityId)); + return findListByStatementAsync(query); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 598897aac5..c13261c003 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -21,17 +21,18 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; @@ -50,14 +51,14 @@ import org.thingsboard.server.dao.tenant.TenantDao; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -import static org.thingsboard.server.dao.DaoUtil.toUUIDs; +import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -88,7 +89,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti @Autowired private CacheManager cacheManager; -// @Cacheable(cacheNames = ENTITY_VIEW_CACHE) @Override public EntityView findEntityViewById(EntityViewId entityViewId) { log.trace("Executing findEntityViewById [{}]", entityViewId); @@ -96,6 +96,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return entityViewDao.findById(entityViewId.getId()); } + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #name}") @Override public EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name) { log.trace("Executing findEntityViewByTenantIdAndName [{}][{}]", tenantId, name); @@ -104,7 +105,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti .orElse(null); } -// @CachePut(cacheNames = ENTITY_VIEW_CACHE) + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.name}") @Override public EntityView saveEntityView(EntityView entityView) { log.trace("Executing save entity view [{}]", entityView); @@ -168,14 +169,11 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti @Override public void deleteEntityView(EntityViewId entityViewId) { log.trace("Executing deleteEntityView [{}]", entityViewId); -// Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); + Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); deleteEntityRelations(entityViewId); EntityView entityView = entityViewDao.findById(entityViewId.getId()); -// List list = new ArrayList<>(); -// list.add(entityView.getTenantId()); -// list.add(entityView.getName()); -// cache.evict(list); + cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getName())); entityViewDao.removeById(entityViewId.getId()); } @@ -188,7 +186,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return new TextPageData<>(entityViews, pageLink); } -// @Cacheable(cacheNames = ENTITY_VIEW_CACHE) @Override public TextPageData findEntityViewByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TextPageLink pageLink) { @@ -228,7 +225,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return new TextPageData<>(entityViews, pageLink); } -// @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #customerId, #entityId, #pageLink}") @Override public TextPageData findEntityViewsByTenantIdAndCustomerIdAndEntityId(TenantId tenantId, CustomerId customerId, @@ -312,9 +308,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti @Override protected void validateDataImpl(EntityView entityView) { - if (StringUtils.isEmpty(entityView.getKeys().toString())) { - throw new DataValidationException("Entity view type should be specified!"); - } if (StringUtils.isEmpty(entityView.getName())) { throw new DataValidationException("Entity view name should be specified!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index a6787d0520..45d0ff9493 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -45,7 +45,6 @@ public class ModelConstants { public static final String SEARCH_TEXT_PROPERTY = "search_text"; public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; public static final String ENTITY_TYPE_PROPERTY = "entity_type"; - /*public static final String ENTITY_VIEW_ID_PROPERTY = "entity_view_id";*/ public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; public static final String ENTITY_ID_COLUMN = "entity_id"; @@ -53,7 +52,6 @@ public class ModelConstants { public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; - /** * Cassandra user constants. */ @@ -148,18 +146,19 @@ public class ModelConstants { * Cassandra entityView constants. */ public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views"; - public static final String ENTITY_VIEW_FAMILY_NAME = "entity-view"; public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN; public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY; - public static final String ENTITY_VIEW_TYPE_PROPERTY = DEVICE_TYPE_PROPERTY; - public static final String ENTITY_VIEW_TENANT_AND_NAME_VIEW_NAME = "entity_view_by_tenant_and_name"; + public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_ENTITY_AND_SEARCH_TEXT = "entity_views_by_tenant_and_customer_and_entity"; + public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_SEARCH_TEXT = "entity_views_by_tenant_and_customer"; public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys"; public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts"; public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts"; public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text"; + public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_views_by_tenant_and_name"; + public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_AND_SEARCH_TEXT = "entity_view_by_tenant_and_entity"; /** * Cassandra audit log constants. diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index 3cefacb970..4645420eb8 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -165,35 +165,55 @@ CREATE TABLE IF NOT EXISTS thingsboard.device ( CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS SELECT * from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND type IS NOT NULL + AND name IS NOT NULL + AND id IS NOT NULL PRIMARY KEY ( tenant_id, name, id, customer_id, type) WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS SELECT * from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND type IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL PRIMARY KEY ( tenant_id, search_text, id, customer_id, type) WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS SELECT * from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND type IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL PRIMARY KEY ( tenant_id, type, search_text, id, customer_id) WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS SELECT * from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND type IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL PRIMARY KEY ( customer_id, tenant_id, search_text, id, type ) WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS SELECT * from thingsboard.device - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND type IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL PRIMARY KEY ( customer_id, tenant_id, type, search_text, id ) WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC ); @@ -651,33 +671,60 @@ CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( end_ts bigint, search_text text, additional_info text, - PRIMARY KEY (id, tenant_id, customer_id) + PRIMARY KEY (id, entity_id, tenant_id, customer_id) ); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_name AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL AND tenant_id IS NOT NULL AND customer_id IS NOT NULL AND keys IS NOT NULL AND start_ts IS NOT NULL AND end_ts IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY (tenant_id, name, id, entity_id, customer_id) - WITH CLUSTERING ORDER BY (name ASC, id DESC, entity_id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_entity AS + WHERE tenant_id IS NOT NULL + AND entity_id IS NOT NULL + AND customer_id IS NOT NULL + AND name IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id) + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); + +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS + SELECT * + from thingsboard.entity_views + WHERE tenant_id IS NOT NULL + AND entity_id IS NOT NULL + AND customer_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id) + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); + +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL AND tenant_id IS NOT NULL AND customer_id IS NOT NULL AND keys IS NOT NULL AND start_ts IS NOT NULL AND end_ts IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY (tenant_id, entity_id, id, customer_id, name) - WITH CLUSTERING ORDER BY (entity_id ASC, customer_id ASC, id DESC, name DESC); + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND entity_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, entity_id, search_text, id, customer_id) + WITH CLUSTERING ORDER BY (entity_id ASC, search_text ASC, id DESC, customer_id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_customer AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL AND tenant_id IS NOT NULL AND customer_id IS NOT NULL AND keys IS NOT NULL AND start_ts IS NOT NULL AND end_ts IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, id, entity_id, name) - WITH CLUSTERING ORDER BY (customer_id ASC, id DESC, entity_id DESC, name DESC); + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND entity_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id) + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_views_by_tenant_and_customer_and_entity AS SELECT * from thingsboard.entity_views - WHERE entity_id IS NOT NULL AND tenant_id IS NOT NULL AND customer_id IS NOT NULL AND keys IS NOT NULL AND start_ts IS NOT NULL AND end_ts IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, entity_id, id, name) - WITH CLUSTERING ORDER BY (customer_id ASC, entity_id DESC, id DESC, name DESC); + WHERE tenant_id IS NOT NULL + AND customer_id IS NOT NULL + AND entity_id IS NOT NULL + AND search_text IS NOT NULL + AND id IS NOT NULL + PRIMARY KEY (tenant_id, customer_id, entity_id, search_text, id) + WITH CLUSTERING ORDER BY (customer_id DESC, entity_id ASC, search_text ASC, id DESC);