diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index af62e04cff..84a8151316 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -318,7 +318,7 @@ public abstract class BaseController { checkUserId(new UserId(entityId.getId())); return; case ENTITY_VIEW: - checkEntityView(entityViewService.findEntityViewById(new EntityViewId(entityId.getId()))); + checkEntityViewId(entityViewService.findEntityViewById(new EntityViewId(entityId.getId()))); return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); @@ -351,14 +351,14 @@ public abstract class BaseController { try { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); EntityView entityView = entityViewService.findEntityViewById(entityViewId); - checkEntityView(entityView); + checkEntityViewId(entityView); return entityView; } catch (Exception e) { throw handleException(e, false); } } - protected void checkEntityView(EntityView entityView) throws ThingsboardException { + protected void checkEntityViewId(EntityView entityView) throws ThingsboardException { checkNotNull(entityView); checkTenantId(entityView.getTenantId()); if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { 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 26a48daff6..e6f149e05c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -17,7 +17,14 @@ package org.thingsboard.server.controller; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; @@ -49,13 +56,10 @@ public class EntityViewController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET) @ResponseBody - public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) - throws ThingsboardException { - + public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { - EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); - return checkEntityViewId(entityViewId); + return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId))); } catch (Exception e) { throw handleException(e); } @@ -70,13 +74,10 @@ public class EntityViewController extends BaseController { EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); logEntityAction(savedEntityView.getId(), savedEntityView, null, entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); - return savedEntityView; - } catch (Exception e) { logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null, entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); - throw handleException(e); } } @@ -90,7 +91,6 @@ public class EntityViewController extends BaseController { EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); EntityView entityView = checkEntityViewId(entityViewId); entityViewService.deleteEntityView(entityViewId); - logEntityAction(entityViewId, entityView, entityView.getCustomerId(), ActionType.DELETED,null, strEntityViewId); } catch (Exception e) { @@ -117,11 +117,9 @@ public class EntityViewController extends BaseController { checkEntityViewId(entityViewId); EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId)); - logEntityAction(entityViewId, savedEntityView, savedEntityView.getCustomerId(), ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName()); - return savedEntityView; } catch (Exception e) { logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, @@ -143,9 +141,7 @@ public class EntityViewController extends BaseController { throw new IncorrectParameterException("Entity View isn't assigned to any customer!"); } Customer customer = checkCustomerId(entityView.getCustomerId()); - EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId)); - logEntityAction(entityViewId, entityView, entityView.getCustomerId(), ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName()); @@ -208,7 +204,7 @@ public class EntityViewController extends BaseController { List entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get()); entityViews = entityViews.stream().filter(entityView -> { try { - checkEntityView(entityView); + checkEntityViewId(entityView); return true; } catch (ThingsboardException e) { return false; 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 9dc13a8d37..ee97b97fa0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -50,6 +50,7 @@ import java.util.Set; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -319,12 +320,55 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes } @Test - public void testTheCopyOfAttrsThatMatchWithDeviceCriteriaForTheView() throws Exception { + public void testTheCopyOfAttrsIntoTSForTheView() throws Exception { + Set actualAttributesSet = + getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}"); - String viewDeviceId = testDevice.getId().getId().toString(); - DeviceCredentials deviceCredentials - = doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); + Set expectedActualAttributesSet = + new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4")); + assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); + Thread.sleep(1000); + + EntityView savedView = getNewSavedEntityView("Test entity view"); + List> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); + + assertEquals("value1", getValue(values, "caValue1")); + assertEquals(true, getValue(values, "caValue2")); + assertEquals(42.0, getValue(values, "caValue3")); + assertEquals(73, getValue(values, "caValue4")); + } + + @Test + public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception { + Set actualAttributesSet = + getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}"); + + Set expectedActualAttributesSet = new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4")); + assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); + Thread.sleep(1000); + + List> valueTelemetryOfDevices = doGetAsync("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); + + EntityView view = new EntityView(); + view.setEntityId(testDevice.getId()); + view.setTenantId(savedTenant.getId()); + view.setName("Test entity view"); + view.setKeys(telemetry); + view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10); + view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10); + EntityView savedView = doPost("/api/entityView", view, EntityView.class); + + List> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); + assertEquals(0, values.size()); + } + private Set getAttributesByKeys(String stringKV) throws Exception { + String viewDeviceId = testDevice.getId().getId().toString(); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); String accessToken = deviceCredentials.getCredentialsId(); @@ -339,34 +383,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Thread.sleep(3000); MqttMessage message = new MqttMessage(); - message.setPayload(("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}").getBytes()); + message.setPayload((stringKV).getBytes()); client.publish("v1/devices/me/attributes", message); Thread.sleep(1000); - List actualTelemetry = - doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class); - Set actualTelemetrySet = new HashSet<>(actualTelemetry); - - List expectedActualTelemetry = Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"); - Set expectedActualTelemetrySet = new HashSet<>(expectedActualTelemetry); - assertTrue(actualTelemetrySet.containsAll(expectedActualTelemetrySet)); - Thread.sleep(1000); - - EntityView savedView = getNewSavedEntityView("Test entity view"); - String urlOfTelemetryValues = "/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualTelemetrySet); - List> values = doGetAsync(urlOfTelemetryValues, List.class); - - assertEquals("value1", getValueOfMap(values, "caValue1")); - assertEquals(true, getValueOfMap(values, "caValue2")); - assertEquals(42.0, getValueOfMap(values, "caValue3")); - assertEquals(73, getValueOfMap(values, "caValue4")); + return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class)); } - private Object getValueOfMap(List> values, String stringValue) { - return values.stream() - .filter(value -> value.get("key").equals(stringValue)) - .findFirst().get().get("value"); + private Object getValue(List> values, String stringValue) { + return values.size() == 0 ? null : + values.stream() + .filter(value -> value.get("key").equals(stringValue)) + .findFirst().get().get("value"); } private EntityView getNewSavedEntityView(String name) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index c8a5da8151..a9e94e9184 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.controller.sql.*Test", + "org.thingsboard.server.controller.sql.EntityViewControllerSqlTest", }) public class ControllerSqlTestSuite { 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 e866bc89b8..395f9022b0 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 @@ -78,7 +78,7 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) { + public List findEntityViewsByTenantId(UUID tenantId, TextPageLink 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, diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java index 742cb92d90..ba433855e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java @@ -45,7 +45,7 @@ public interface EntityViewDao extends Dao { * @param pageLink the page link * @return the list of entity view objects */ - List findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink); + List findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink); /** * Find entity views by tenantId and entity view name. diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java index 89b7e2f85c..19f326c0bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java @@ -32,28 +32,27 @@ import java.util.List; */ public interface EntityViewService { - EntityView findEntityViewById(EntityViewId entityViewId); - EntityView saveEntityView(EntityView entityView); EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId); EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId); - void deleteEntityView(EntityViewId entityViewId); + void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId); - TextPageData findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); + EntityView findEntityViewById(EntityViewId entityViewId); - void deleteEntityViewsByTenantId(TenantId tenantId); + TextPageData findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); - TextPageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, - TextPageLink pageLink); + TextPageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); - void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId); + ListenableFuture> findEntityViewsByQuery(EntityViewSearchQuery query); ListenableFuture findEntityViewByIdAsync(EntityViewId entityViewId); - ListenableFuture> findEntityViewsByQuery(EntityViewSearchQuery query); - ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId); + + void deleteEntityView(EntityViewId entityViewId); + + void deleteEntityViewsByTenantId(TenantId tenantId); } 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 8647af7fe4..6e7eba08c8 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 @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.entityview; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -25,6 +24,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; @@ -49,18 +49,17 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantDao; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validatePageLink; -import static org.thingsboard.server.dao.service.Validator.validateString; /** * Created by Victor Basanets on 8/28/2017. @@ -89,60 +88,30 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti @Autowired private CacheManager cacheManager; - @Override - public EntityView findEntityViewById(EntityViewId entityViewId) { - log.trace("Executing findEntityViewById [{}]", entityViewId); - validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); - return entityViewDao.findById(entityViewId.getId()); - } - - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.name}") + @Caching(evict = { + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"), + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")}) @Override public EntityView saveEntityView(EntityView entityView) { log.trace("Executing save entity view [{}]", entityView); entityViewValidator.validate(entityView); EntityView savedEntityView = entityViewDao.save(entityView); + + List>> futures = new ArrayList<>(); if (savedEntityView.getKeys() != null) { - copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs()); - copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs()); - copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh()); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs())); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs())); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh())); } - return savedEntityView; - } - - private void copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys) { - if (keys != null && !keys.isEmpty()) { - ListenableFuture> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); - Futures.addCallback(getAttrFuture, new FutureCallback>() { - @Override - public void onSuccess(@Nullable List attributeKvEntries) { - if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { - List filteredAttributes = - attributeKvEntries.stream() - .filter(attributeKvEntry -> { - if (entityView.getStartTimeMs() == 0 && entityView.getEndTimeMs() == 0) { - return true; - } - if (entityView.getEndTimeMs() == 0 && entityView.getStartTimeMs() < attributeKvEntry.getLastUpdateTs()) { - return true; - } - if (entityView.getStartTimeMs() == 0 && entityView.getEndTimeMs() > attributeKvEntry.getLastUpdateTs()) { - return true; - } - return entityView.getStartTimeMs() < attributeKvEntry.getLastUpdateTs() - && entityView.getEndTimeMs() > attributeKvEntry.getLastUpdateTs(); - }).collect(Collectors.toList()); - attributesService.save(entityView.getId(), scope, filteredAttributes); - } - } - - @Override - public void onFailure(Throwable throwable) { - log.error("Failed to fetch [{}] attributes [{}] for [{}] entity [{}]", - scope, keys, entityView.getEntityId().getEntityType().name(), entityView.getEntityId().getId().toString(), throwable); - } - }); + for (ListenableFuture> future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to copy attributes to entity view", e); + throw new RuntimeException("Failed to copy attributes to entity view", e); + } } + return savedEntityView; } @Override @@ -152,6 +121,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return saveEntityView(entityView); } + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId) { EntityView entityView = findEntityViewById(entityViewId); @@ -160,63 +130,43 @@ 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); + public void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId) { + log.trace("Executing unassignCustomerEntityViews, tenantId [{}], customerId [{}]", tenantId, customerId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + new CustomerEntityViewsUnAssigner(tenantId).removeEntities(customerId); + } + + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") + @Override + public EntityView findEntityViewById(EntityViewId entityViewId) { + log.trace("Executing findEntityViewById [{}]", entityViewId); validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); - deleteEntityRelations(entityViewId); - EntityView entityView = entityViewDao.findById(entityViewId.getId()); - cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getName())); - entityViewDao.removeById(entityViewId.getId()); + return entityViewDao.findById(entityViewId.getId()); } @Override public TextPageData findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) { - log.trace("Executing findEntityViewByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List entityViews = entityViewDao.findEntityViewByTenantId(tenantId.getId(), pageLink); + List entityViews = entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink); return new TextPageData<>(entityViews, pageLink); } - @Override - public void deleteEntityViewsByTenantId(TenantId tenantId) { - log.trace("Executing deleteEntityViewsByTenantId, tenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - tenantEntityViewRemover.removeEntities(tenantId); - } - @Override public TextPageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, - TextPageLink pageLink) { - + TextPageLink pageLink) { log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," + - " pageLink [{}]", tenantId, customerId, pageLink); - + " pageLink [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); List entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - return new TextPageData<>(entityViews, pageLink); } - @Override - public void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId) { - log.trace("Executing unassignCustomerEntityViews, tenantId [{}], customerId [{}]", tenantId, customerId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - new CustomerEntityViewsUnAssigner(tenantId).removeEntities(customerId); - } - - @Override - public ListenableFuture findEntityViewByIdAsync(EntityViewId entityViewId) { - log.trace("Executing findEntityViewById [{}]", entityViewId); - validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); - return entityViewDao.findByIdAsync(entityViewId.getId()); - } - @Override public ListenableFuture> findEntityViewsByQuery(EntityViewSearchQuery query) { ListenableFuture> relations = relationService.findByQuery(query.toEntitySearchQuery()); @@ -231,10 +181,17 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti } return Futures.successfulAsList(futures); }); - return entityViews; } + @Override + public ListenableFuture findEntityViewByIdAsync(EntityViewId entityViewId) { + log.trace("Executing findEntityViewById [{}]", entityViewId); + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); + return entityViewDao.findByIdAsync(entityViewId.getId()); + } + + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #entityId}") @Override public ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) { log.trace("Executing findEntityViewsByTenantIdAndEntityIdAsync, tenantId [{}], entityId [{}]", tenantId, entityId); @@ -243,13 +200,62 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return entityViewDao.findEntityViewsByTenantIdAndEntityIdAsync(tenantId.getId(), entityId.getId()); } + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") + @Override + public void deleteEntityView(EntityViewId entityViewId) { + log.trace("Executing deleteEntityView [{}]", entityViewId); + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); + deleteEntityRelations(entityViewId); + Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); + EntityView entityView = entityViewDao.findById(entityViewId.getId()); + cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); + entityViewDao.removeById(entityViewId.getId()); + } + + @Override + public void deleteEntityViewsByTenantId(TenantId tenantId) { + log.trace("Executing deleteEntityViewsByTenantId, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + tenantEntityViewRemover.removeEntities(tenantId); + } + + private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys) { + if (keys != null && !keys.isEmpty()) { + ListenableFuture> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); + return Futures.transform(getAttrFuture, attributeKvEntries -> { + List filteredAttributes = new ArrayList<>(); + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { + filteredAttributes = + attributeKvEntries.stream() + .filter(attributeKvEntry -> { + long startTime = entityView.getStartTimeMs(); + long endTime = entityView.getEndTimeMs(); + long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); + return startTime == 0 && endTime == 0 || + (endTime == 0 && startTime < lastUpdateTs) || + (startTime == 0 && endTime > lastUpdateTs) + ? true : startTime < lastUpdateTs && endTime > lastUpdateTs; + }).collect(Collectors.toList()); + } + try { + return attributesService.save(entityView.getId(), scope, filteredAttributes).get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to copy attributes to entity view", e); + throw new RuntimeException("Failed to copy attributes to entity view", e); + } + }); + } else { + return Futures.immediateFuture(null); + } + } + private DataValidator entityViewValidator = new DataValidator() { @Override protected void validateCreate(EntityView entityView) { entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) - .ifPresent( e -> { + .ifPresent(e -> { throw new DataValidationException("Entity view with such name already exists!"); }); } @@ -257,7 +263,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti @Override protected void validateUpdate(EntityView entityView) { entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) - .ifPresent( e -> { + .ifPresent(e -> { if (!e.getUuidId().equals(entityView.getUuidId())) { throw new DataValidationException("Entity view with such name already exists!"); } @@ -296,7 +302,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti @Override protected List findEntities(TenantId id, TextPageLink pageLink) { - return entityViewDao.findEntityViewByTenantId(id.getId(), pageLink); + return entityViewDao.findEntityViewsByTenantId(id.getId(), pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java index 0cd1b2b0d6..912c9d5eed 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java @@ -66,7 +66,7 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) { + public List findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) { return DaoUtil.convertDataList( entityViewRepository.findByTenantId( fromTimeUUID(tenantId), diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index d609620edf..c5dcc0f62d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -15,9 +15,7 @@ */ package org.thingsboard.rule.engine.action; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Function; -import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; @@ -28,24 +26,23 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.util.DonAsynchron; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback; +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @Slf4j @RuleNode( @@ -70,25 +67,22 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) || - msg.getType().equals(DataConstants.ATTRIBUTES_DELETED) || - msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { + if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); - String scope; - if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { - scope = DataConstants.CLIENT_SCOPE; - } else { - scope = msg.getMetaData().getValue("scope"); - } + String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? + DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope"); + ListenableFuture> entityViewsFuture = ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); - withCallback(entityViewsFuture, + + DonAsynchron.withCallback(entityViewsFuture, entityViews -> { - List>> saveFutures = new ArrayList<>(); for (EntityView entityView : entityViews) { - if ((entityView.getEndTimeMs() != 0 && entityView.getEndTimeMs() > now && entityView.getStartTimeMs() < now) || - (entityView.getEndTimeMs() == 0 && entityView.getStartTimeMs() < now)) { - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes(); + long startTime = entityView.getStartTimeMs(); + long endTime = entityView.getEndTimeMs(); + if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { + Set attributes = + JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes(); List filteredAttributes = attributes.stream() .filter(attr -> { @@ -110,19 +104,22 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { return entityView.getKeys().getAttributes().getSh().contains(attr.getKey()); } return false; - }) - .collect(Collectors.toList()); - saveFutures.add(ctx.getAttributesService().save(entityView.getId(), scope, new ArrayList<>(filteredAttributes))); + }).collect(Collectors.toList()); + + ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes, + new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + ctx.tellNext(msg, SUCCESS); + } + + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }); } } - Futures.transform(Futures.allAsList(saveFutures), new Function>, Object>() { - @Nullable - @Override - public Object apply(@Nullable List> lists) { - ctx.tellNext(msg, TbRelationTypes.SUCCESS); - return null; - } - }); }, t -> ctx.tellFailure(msg, t)); } else {