thingsboard-aplcache

Details

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 af62e04..84a8151 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 26a48da..e6f149e 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<EntityView> 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 9dc13a8..ee97b97 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<String> 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<String> expectedActualAttributesSet =
+                new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
+        assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
+        Thread.sleep(1000);
+
+        EntityView savedView = getNewSavedEntityView("Test entity view");
+        List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
+                "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
+
+        assertEquals("value1", getValue(values, "caValue1"));
+        assertEquals(true, getValue(values, "caValue2"));
+        assertEquals(42.0, getValue(values, "caValue3"));
+        assertEquals(73, getValue(values, "caValue4"));
+    }
+
+    @Test
+    public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception {
+        Set<String> actualAttributesSet =
+                getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
+
+        Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
+        assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
+        Thread.sleep(1000);
+
+        List<Map<String, Object>> valueTelemetryOfDevices = doGetAsync("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() +
+                "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
+
+        EntityView view = new EntityView();
+        view.setEntityId(testDevice.getId());
+        view.setTenantId(savedTenant.getId());
+        view.setName("Test entity view");
+        view.setKeys(telemetry);
+        view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
+        view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
+        EntityView savedView = doPost("/api/entityView", view, EntityView.class);
+
+        List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
+                "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
+        assertEquals(0, values.size());
+    }
 
+    private Set<String> getAttributesByKeys(String stringKV) throws Exception {
+        String viewDeviceId = testDevice.getId().getId().toString();
+        DeviceCredentials deviceCredentials =
+                doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class);
         assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
 
         String accessToken = deviceCredentials.getCredentialsId();
@@ -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<String> actualTelemetry =
-                doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId +  "/keys/attributes", List.class);
-        Set<String> actualTelemetrySet = new HashSet<>(actualTelemetry);
-
-        List<String> expectedActualTelemetry = Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4");
-        Set<String> 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<Map<String, Object>> 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<Map<String, Object>> values, String stringValue) {
-        return values.stream()
-                .filter(value -> value.get("key").equals(stringValue))
-                .findFirst().get().get("value");
+    private Object getValue(List<Map<String, Object>> values, String stringValue) {
+        return values.size() == 0 ? null :
+                values.stream()
+                        .filter(value -> value.get("key").equals(stringValue))
+                        .findFirst().get().get("value");
     }
 
     private EntityView getNewSavedEntityView(String name) throws Exception {
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 c8a5da8..a9e94e9 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 e866bc8..395f902 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<Entit
     }
 
     @Override
-    public List<EntityView> findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) {
+    public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
         log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
         List<EntityViewEntity> entityViewEntities =
                 findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
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 742cb92..ba43385 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<EntityView> {
      * @param pageLink the page link
      * @return the list of entity view objects
      */
-    List<EntityView> findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink);
+    List<EntityView> 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 89b7e2f..19f326c 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<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
+    EntityView findEntityViewById(EntityViewId entityViewId);
 
-    void deleteEntityViewsByTenantId(TenantId tenantId);
+    TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
 
-    TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
-                                                                    TextPageLink pageLink);
+    TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
 
-    void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId);
+    ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
 
     ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
 
-    ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
-
     ListenableFuture<List<EntityView>> 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 8647af7..6e7eba0 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<ListenableFuture<List<Void>>> 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<String> keys) {
-        if (keys != null && !keys.isEmpty()) {
-            ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
-            Futures.addCallback(getAttrFuture, new FutureCallback<List<AttributeKvEntry>>() {
-                @Override
-                public void onSuccess(@Nullable List<AttributeKvEntry> attributeKvEntries) {
-                    if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
-                        List<AttributeKvEntry> 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<List<Void>> future : futures) {
+            try {
+                future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                log.error("Failed to copy attributes to entity view", e);
+                throw new RuntimeException("Failed to copy attributes to entity view", e);
+            }
         }
+        return savedEntityView;
     }
 
     @Override
@@ -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,64 +130,44 @@ 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<EntityView> 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<EntityView> entityViews = entityViewDao.findEntityViewByTenantId(tenantId.getId(), pageLink);
+        List<EntityView> 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<EntityView> 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<EntityView> 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<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId) {
-        log.trace("Executing findEntityViewById [{}]", entityViewId);
-        validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
-        return entityViewDao.findByIdAsync(entityViewId.getId());
-    }
-
-    @Override
     public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
         ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
         ListenableFuture<List<EntityView>> entityViews = Futures.transformAsync(relations, r -> {
@@ -231,11 +181,18 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
             }
             return Futures.successfulAsList(futures);
         });
-
         return entityViews;
     }
 
     @Override
+    public ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId) {
+        log.trace("Executing findEntityViewById [{}]", entityViewId);
+        validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
+        return entityViewDao.findByIdAsync(entityViewId.getId());
+    }
+
+    @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #entityId}")
+    @Override
     public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) {
         log.trace("Executing findEntityViewsByTenantIdAndEntityIdAsync, tenantId [{}], entityId [{}]", tenantId, entityId);
         validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
@@ -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<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
+        if (keys != null && !keys.isEmpty()) {
+            ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
+            return Futures.transform(getAttrFuture, attributeKvEntries -> {
+                List<AttributeKvEntry> filteredAttributes = new ArrayList<>();
+                if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
+                    filteredAttributes =
+                            attributeKvEntries.stream()
+                                    .filter(attributeKvEntry -> {
+                                        long startTime = entityView.getStartTimeMs();
+                                        long endTime = entityView.getEndTimeMs();
+                                        long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
+                                        return startTime == 0 && endTime == 0 ||
+                                                (endTime == 0 && startTime < lastUpdateTs) ||
+                                                (startTime == 0 && endTime > lastUpdateTs)
+                                                ? true : startTime < lastUpdateTs && endTime > lastUpdateTs;
+                                    }).collect(Collectors.toList());
+                }
+                try {
+                    return attributesService.save(entityView.getId(), scope, filteredAttributes).get();
+                } catch (InterruptedException | ExecutionException e) {
+                    log.error("Failed to copy attributes to entity view", e);
+                    throw new RuntimeException("Failed to copy attributes to entity view", e);
+                }
+            });
+        } else {
+            return Futures.immediateFuture(null);
+        }
+    }
+
     private DataValidator<EntityView> entityViewValidator =
             new DataValidator<EntityView>() {
 
                 @Override
                 protected void validateCreate(EntityView entityView) {
                     entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
-                            .ifPresent( e -> {
+                            .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<EntityView> 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 0cd1b2b..912c9d5 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<EntityViewEntity,
     }
 
     @Override
-    public List<EntityView> findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) {
+    public List<EntityView> 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 d609620..c5dcc0f 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<List<EntityView>> entityViewsFuture =
                     ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator());
-            withCallback(entityViewsFuture,
+
+            DonAsynchron.withCallback(entityViewsFuture,
                     entityViews -> {
-                        List<ListenableFuture<List<Void>>> saveFutures = new ArrayList<>();
                         for (EntityView entityView : entityViews) {
-                            if ((entityView.getEndTimeMs() != 0  && entityView.getEndTimeMs() > now && entityView.getStartTimeMs() < now) ||
-                                    (entityView.getEndTimeMs() == 0 && entityView.getStartTimeMs() < now)) {
-                                Set<AttributeKvEntry> 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<AttributeKvEntry> attributes =
+                                        JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes();
                                 List<AttributeKvEntry> 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<Void>() {
+                                            @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<List<List<Void>>, Object>() {
-                            @Nullable
-                            @Override
-                            public Object apply(@Nullable List<List<Void>> lists) {
-                                ctx.tellNext(msg, TbRelationTypes.SUCCESS);
-                                return null;
-                            }
-                        });
                     },
                     t -> ctx.tellFailure(msg, t));
         } else {