thingsboard-memoizeit

Changes

Details

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
new file mode 100644
index 0000000..d9ba517
--- /dev/null
+++ b/application/src/main/data/upgrade/2.1.1/schema_update.cql
@@ -0,0 +1,92 @@
+--
+-- Copyright © 2016-2018 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_views_by_tenant_and_name;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_views_by_tenant_and_entity;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_views_by_tenant_and_customer;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_views_by_tenant_and_customer_and_entity;
+
+DROP TABLE IF EXISTS thingsboard.entity_views;
+
+CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
+    id timeuuid,
+    entity_id timeuuid,
+    tenant_id timeuuid,
+    customer_id timeuuid,
+    name text,
+    keys text,
+    ts_begin bigint,
+    ts_end bigint,
+    search_text text,
+    additional_info text,
+    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 ts_begin IS NOT NULL
+    AND ts_end 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
+    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 ts_begin IS NOT NULL
+    AND ts_end 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);
+
+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 ts_begin IS NOT NULL
+    AND ts_end 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);
+
+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 ts_begin IS NOT NULL
+    AND ts_end 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);
diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.sql b/application/src/main/data/upgrade/2.1.1/schema_update.sql
new file mode 100644
index 0000000..3670aad
--- /dev/null
+++ b/application/src/main/data/upgrade/2.1.1/schema_update.sql
@@ -0,0 +1,31 @@
+--
+-- Copyright © 2016-2018 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+DROP TABLE IF EXISTS entity_views;
+
+CREATE TABLE IF NOT EXISTS entity_views (
+    id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
+    entity_id varchar(31),
+    entity_type varchar(255),
+    tenant_id varchar(31),
+    customer_id varchar(31),
+    name varchar(255),
+    keys varchar(255),
+    ts_begin varchar(255),
+    ts_end varchar(255),
+    search_text varchar(255),
+    additional_info varchar
+);
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 de73fe0..af62e04 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -56,6 +56,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
 import org.thingsboard.server.dao.dashboard.DashboardService;
 import org.thingsboard.server.dao.device.DeviceCredentialsService;
 import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.entityview.EntityViewService;
 import org.thingsboard.server.dao.exception.DataValidationException;
 import org.thingsboard.server.dao.exception.IncorrectParameterException;
 import org.thingsboard.server.dao.model.ModelConstants;
@@ -139,6 +140,9 @@ public abstract class BaseController {
     @Autowired
     protected DeviceStateService deviceStateService;
 
+    @Autowired
+    protected EntityViewService entityViewService;
+
     @ExceptionHandler(ThingsboardException.class)
     public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
         errorResponseHandler.handle(ex, response);
@@ -313,6 +317,9 @@ public abstract class BaseController {
                 case USER:
                     checkUserId(new UserId(entityId.getId()));
                     return;
+                case ENTITY_VIEW:
+                    checkEntityView(entityViewService.findEntityViewById(new EntityViewId(entityId.getId())));
+                    return;
                 default:
                     throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
             }
@@ -340,6 +347,25 @@ public abstract class BaseController {
         }
     }
 
+    protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException {
+        try {
+            validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
+            EntityView entityView = entityViewService.findEntityViewById(entityViewId);
+            checkEntityView(entityView);
+            return entityView;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    protected void checkEntityView(EntityView entityView) throws ThingsboardException {
+        checkNotNull(entityView);
+        checkTenantId(entityView.getTenantId());
+        if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+            checkCustomerId(entityView.getCustomerId());
+        }
+    }
+
     Asset checkAssetId(AssetId assetId) throws ThingsboardException {
         try {
             validateId(assetId, "Incorrect assetId " + assetId);
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
new file mode 100644
index 0000000..fd3ccf2
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.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.EntityType;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityViewId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Victor Basanets on 8/28/2017.
+ */
+@RestController
+@RequestMapping("/api")
+public class EntityViewController extends BaseController {
+
+    public static final String ENTITY_VIEW_ID = "entityViewId";
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/entity-view/{entityViewId}", method = RequestMethod.GET)
+    @ResponseBody
+    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);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/entity-view", method = RequestMethod.POST)
+    @ResponseBody
+    public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException {
+        try {
+            entityView.setTenantId(getCurrentUser().getTenantId());
+            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);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/entity-view/{entityViewId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteEntityView(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
+        checkParameter(ENTITY_VIEW_ID, strEntityViewId);
+        try {
+            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) {
+            logEntityAction(emptyId(EntityType.ENTITY_VIEW),
+                    null,
+                    null,
+                    ActionType.DELETED, e, strEntityViewId);
+            throw handleException(e);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index f863d0b..347b054 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -88,6 +88,13 @@ public class ThingsboardInstallService {
 
                         dataUpdateService.updateData("1.4.0");
 
+                    case "2.0.0":
+                        log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ...");
+
+                        databaseUpgradeService.upgradeDatabase("2.0.0");
+
+                        dataUpdateService.updateData("2.0.0");
+
                         log.info("Updating system data...");
 
                         systemDataLoaderService.deleteSystemWidgetBundle("charts");
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java
index 5daebcc..0194a5d 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java
@@ -49,6 +49,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
                 log.info("Updating data from version 1.4.0 to 2.0.0 ...");
                 tenantsDefaultRuleChainUpdater.updateEntities(null);
                 break;
+            case "2.0.0":
+                log.info("Updating data from version 2.0.0 to 2.1.1 ...");
+                tenantsDefaultRuleChainUpdater.updateEntities(null);
+                break;
             default:
                 throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
         }
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index 3a4a837..7d701b7 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -107,6 +107,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
                     log.info("Schema updated.");
                 }
                 break;
+            case "2.0.0":
+                try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
+                    log.info("Updating schema ...");
+                    schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_SQL);
+                    loadSql(schemaUpdateFile, conn);
+                    log.info("Schema updated.");
+                }
+                break;
+
             default:
                 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
         }
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 743a860..be5149d 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -359,7 +359,7 @@ spring:
     password: "${SPRING_DATASOURCE_PASSWORD:}"
 
 # PostgreSQL DAO Configuration
-#spring:
+# spring:
 #  data:
 #    sql:
 #      repositories:
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
new file mode 100644
index 0000000..c7ac98d
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+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.objects.AttributesEntityView;
+import org.thingsboard.server.common.data.objects.TelemetryEntityView;
+import org.thingsboard.server.common.data.security.Authority;
+
+import java.util.Arrays;
+
+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 Tenant savedTenant;
+    private User tenantAdmin;
+    private Device testDevice;
+    private TelemetryEntityView obj;
+
+    @Before
+    public void beforeTest() throws Exception {
+        loginSysAdmin();
+
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        savedTenant = doPost("/api/tenant", tenant, Tenant.class);
+
+        Assert.assertNotNull(savedTenant);
+
+        tenantAdmin = new User();
+        tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
+        tenantAdmin.setTenantId(savedTenant.getId());
+        tenantAdmin.setEmail("tenant2@thingsboard.org");
+        tenantAdmin.setFirstName("Joe");
+        tenantAdmin.setLastName("Downs");
+
+        tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
+
+        Device device = new Device();
+        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")));
+    }
+
+    @After
+    public void afterTest() throws Exception {
+        loginSysAdmin();
+
+        doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
+                .andExpect(status().isOk());
+    }
+
+    @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);
+        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);
+
+        Assert.assertNotNull(savedView);
+        Assert.assertNotNull(savedView.getId());
+        Assert.assertTrue(savedView.getCreatedTime() > 0);
+        Assert.assertEquals(savedTenant.getId(), savedView.getTenantId());
+        Assert.assertNotNull(savedView.getCustomerId());
+        Assert.assertEquals(NULL_UUID, savedView.getCustomerId().getId());
+        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);
+
+        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);
+
+        doDelete("/api/entity-view/" + savedView.getId().getId().toString())
+                .andExpect(status().isOk());
+
+        doGet("/api/entity-view/" + savedView.getId().getId().toString())
+                .andExpect(status().isNotFound());
+    }
+}
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 f316051..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.*SqlTest",
+        "org.thingsboard.server.controller.sql.EntityViewControllerSqlTest",
         })
 public class ControllerSqlTestSuite {
 
diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java
new file mode 100644
index 0000000..ad066fc
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller.nosql;
+
+import org.thingsboard.server.controller.BaseEntityViewControllerTest;
+
+/**
+ * Created by Victor Basanets on 8/27/2017.
+ */
+public class EntityViewControllerNoSqlTest extends BaseEntityViewControllerTest {
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
new file mode 100644
index 0000000..76d9925
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller.sql;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.controller.BaseEntityViewControllerTest;
+import org.thingsboard.server.dao.service.DaoSqlTest;
+
+import java.util.Arrays;
+
+/**
+ * Created by Victor Basanets on 8/27/2017.
+ */
+@DaoSqlTest
+public class EntityViewControllerSqlTest extends BaseEntityViewControllerTest {
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
index 21de402..698a69e 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
@@ -20,4 +20,5 @@ public class CacheConstants {
     public static final String RELATIONS_CACHE = "relations";
     public static final String DEVICE_CACHE = "devices";
     public static final String ASSET_CACHE = "assets";
+    public static final String ENTITY_VIEW_CACHE = "entityViews";
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
index fe9c018..ef4994a 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
@@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
  * @author Andrew Shvayka
  */
 public enum EntityType {
-    TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE;
+    TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
new file mode 100644
index 0000000..813a9ac
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data;
+
+import lombok.*;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EntityViewId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.objects.TelemetryEntityView;
+
+/**
+ * Created by Victor Basanets on 8/27/2017.
+ */
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
+        implements HasName, HasTenantId, HasCustomerId {
+
+    private static final long serialVersionUID = 5582010124562018986L;
+
+    private EntityId entityId;
+    private TenantId tenantId;
+    private CustomerId customerId;
+    private String name;
+    private TelemetryEntityView keys;
+    private Long tsBegin;
+    private Long tsEnd;
+
+    public EntityView() {
+        super();
+    }
+
+    public EntityView(EntityViewId id) {
+        super(id);
+    }
+
+    public EntityView(EntityView entityView) {
+        super(entityView);
+    }
+
+    @Override
+    public String getSearchText() {
+        return getName() /*What the ...*/;
+    }
+
+    @Override
+    public CustomerId getCustomerId() {
+        return customerId;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public TenantId getTenantId() {
+        return tenantId;
+    }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
index ed4cf2f..4e35c0b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
@@ -57,6 +57,8 @@ public class EntityIdFactory {
                 return new RuleChainId(uuid);
             case RULE_NODE:
                 return new RuleNodeId(uuid);
+            case ENTITY_VIEW:
+                return new EntityViewId(uuid);
         }
         throw new IllegalArgumentException("EntityType " + type + " is not supported!");
     }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java
new file mode 100644
index 0000000..459dd99
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data.id;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.thingsboard.server.common.data.EntityType;
+
+import java.util.UUID;
+
+/**
+ * Created by Victor Basanets on 8/27/2017.
+ */
+public class EntityViewId extends UUIDBased implements EntityId {
+
+    private static final long serialVersionUID = 1L;
+
+    @JsonCreator
+    public EntityViewId(@JsonProperty("id") UUID id) {
+        super(id);
+    }
+
+    public static EntityViewId fromString(String entityViewID) {
+        return new EntityViewId(UUID.fromString(entityViewID));
+    }
+
+    @Override
+    public EntityType getEntityType() {
+        return EntityType.ENTITY_VIEW;
+    }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java
new file mode 100644
index 0000000..b1d270c
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data.objects;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Victor Basanets on 9/05/2017.
+ */
+@Data
+@NoArgsConstructor
+public class AttributesEntityView {
+
+    private List<String> cs = new ArrayList<>();
+    private List<String> ss = new ArrayList<>();
+    private List<String> sh = new ArrayList<>();
+
+    public AttributesEntityView(List<String> cs,
+                                List<String> ss,
+                                List<String> sh) {
+
+        this.cs = new ArrayList<>(cs);
+        this.ss = new ArrayList<>(ss);
+        this.sh = new ArrayList<>(sh);
+    }
+
+    public AttributesEntityView(AttributesEntityView obj) {
+        this(obj.getCs(), obj.getSs(), obj.getSh());
+    }
+
+    @Override
+    public String toString() {
+        return "{cs=" + cs + ", ss=" + ss + ", sh=" + sh + '}';
+    }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java
new file mode 100644
index 0000000..e7398e6
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data.objects;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Victor Basanets on 9/05/2017.
+ */
+@Data
+@NoArgsConstructor
+public class TelemetryEntityView {
+
+    private List<String> timeseries;
+    private AttributesEntityView attributes;
+
+    public TelemetryEntityView(List<String> timeseries, AttributesEntityView attributes) {
+
+        this.timeseries = new ArrayList<>(timeseries);
+        this.attributes = attributes;
+    }
+
+    public TelemetryEntityView(TelemetryEntityView obj) {
+        this(obj.getTimeseries(), obj.getAttributes());
+    }
+
+    @Override
+    public String toString() {
+        return "{timeseries=" + timeseries + ", attributes=" + attributes + '}';
+    }
+}
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
new file mode 100644
index 0000000..25404db
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.entityview;
+
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.Dao;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Created by Victor Basanets on 8/28/2017.
+ */
+public interface EntityViewDao extends Dao<EntityView> {
+
+    /**
+     * Find entity views by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of entity view objects
+     */
+    List<EntityView> findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink);
+
+    /**
+     * Find entity views by tenantId and entity view name.
+     *
+     * @param tenantId the tenantId
+     * @param name the entity view name
+     * @return the optional entity view object
+     */
+    Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name);
+
+    /**
+     * Find entity views by tenantId, entityId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param entityId the entityId
+     * @param pageLink the page link
+     * @return the list of entity view objects
+     */
+    List<EntityView> findEntityViewByTenantIdAndEntityId(UUID tenantId,
+                                                         UUID entityId,
+                                                         TextPageLink pageLink);
+
+    /**
+     * Find entity views by tenantId, customerId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param customerId the customerId
+     * @param pageLink the page link
+     * @return the list of entity view objects
+     */
+    List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId,
+                                                            UUID customerId,
+                                                            TextPageLink pageLink);
+
+    /**
+     * Find entity views by tenantId, customerId, entityId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param customerId the customerId
+     * @param entityId the entityId
+     * @param pageLink the page link
+     * @return the list of entity view objects
+     */
+    List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndEntityId(UUID tenantId,
+                                                                       UUID customerId,
+                                                                       UUID entityId,
+                                                                       TextPageLink pageLink);
+
+}
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
new file mode 100644
index 0000000..943d35e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.entityview;
+
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+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;
+
+/**
+ * Created by Victor Basanets on 8/27/2017.
+ */
+public interface EntityViewService {
+
+    EntityView findEntityViewById(EntityViewId entityViewId);
+
+    EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name);
+
+    EntityView saveEntityView(EntityView entityView);
+
+    EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId);
+
+    EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId);
+
+    void deleteEntityView(EntityViewId entityViewId);
+
+    TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
+
+    TextPageData<EntityView> findEntityViewByTenantIdAndEntityId(TenantId tenantId, EntityId entityId,
+                                                                 TextPageLink pageLink);
+
+    void deleteEntityViewByTenantId(TenantId tenantId);
+
+    TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
+                                                                    TextPageLink pageLink);
+
+    TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndEntityId(TenantId tenantId,
+                                                                               CustomerId customerId,
+                                                                               EntityId entityId,
+                                                                               TextPageLink pageLink);
+
+    void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId);
+}
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
new file mode 100644
index 0000000..6554083
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
@@ -0,0 +1,276 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.entityview;
+
+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.stereotype.Service;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
+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.customer.CustomerDao;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.
+ */
+@Service
+@Slf4j
+public class EntityViewServiceImpl extends AbstractEntityService
+        implements EntityViewService {
+
+    public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
+    public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
+    public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
+    public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId ";
+
+    @Autowired
+    private EntityViewDao entityViewDao;
+
+    @Autowired
+    private TenantDao tenantDao;
+
+    @Autowired
+    private CustomerDao customerDao;
+
+    @Override
+    public EntityView findEntityViewById(EntityViewId entityViewId) {
+        log.trace("Executing findEntityViewById [{}]", entityViewId);
+        validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
+        return entityViewDao.findById(entityViewId.getId());
+    }
+
+    @Override
+    public EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name) {
+        log.trace("Executing findEntityViewByTenantIdAndName [{}][{}]", tenantId, name);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        return entityViewDao.findEntityViewByTenantIdAndName(tenantId.getId(), name)
+                .orElse(null);
+    }
+
+    @Override
+    public EntityView saveEntityView(EntityView entityView) {
+        log.trace("Executing save entity view [{}]", entityView);
+        entityViewValidator.validate(entityView);
+        return entityViewDao.save(entityView);
+    }
+
+    @Override
+    public EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId) {
+        EntityView entityView = findEntityViewById(entityViewId);
+        entityView.setCustomerId(customerId);
+        return saveEntityView(entityView);
+    }
+
+    @Override
+    public EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId) {
+        EntityView entityView = findEntityViewById(entityViewId);
+        entityView.setCustomerId(null);
+        return saveEntityView(entityView);
+    }
+
+    @Override
+    public void deleteEntityView(EntityViewId entityViewId) {
+        log.trace("Executing deleteEntityView [{}]", entityViewId);
+        validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
+        deleteEntityRelations(entityViewId);
+        EntityView entityView = entityViewDao.findById(entityViewId.getId());
+        List<Object> list = new ArrayList<>();
+        list.add(entityView.getTenantId());
+        list.add(entityView.getName());
+        entityViewDao.removeById(entityViewId.getId());
+    }
+
+    @Override
+    public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findEntityViewByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
+        List<EntityView> entityViews = entityViewDao.findEntityViewByTenantId(tenantId.getId(), pageLink);
+        return new TextPageData<>(entityViews, pageLink);
+    }
+
+    @Override
+    public TextPageData<EntityView> findEntityViewByTenantIdAndEntityId(TenantId tenantId, EntityId entityId,
+                                                                    TextPageLink pageLink) {
+
+        log.trace("Executing findEntityViewByTenantIdAndType, tenantId [{}], entityId [{}], pageLink [{}]",
+                tenantId, entityId, pageLink);
+
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validateString(entityId.toString(), "Incorrect entityId " + entityId.toString());
+        validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
+        List<EntityView> entityViews = entityViewDao.findEntityViewByTenantIdAndEntityId(tenantId.getId(),
+                entityId.getId(), pageLink);
+
+        return new TextPageData<>(entityViews, pageLink);
+    }
+
+    @Override
+    public void deleteEntityViewByTenantId(TenantId tenantId) {
+        log.trace("Executing deleteEntityViewByTenantId, tenantId [{}]", tenantId);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        tenantEntityViewRemover.removeEntities(tenantId);
+    }
+
+    @Override
+    public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
+                                                                          TextPageLink pageLink) {
+
+        log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," +
+                        " 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 TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndEntityId(TenantId tenantId,
+                                                                                      CustomerId customerId,
+                                                                                      EntityId entityId,
+                                                                                      TextPageLink pageLink) {
+
+        log.trace("Executing findEntityViewsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}]," +
+                " entityId [{}], pageLink [{}]", tenantId, customerId, entityId, pageLink);
+
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
+        validateString(entityId.toString(), "Incorrect entityId " + entityId.toString());
+        validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
+        List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerIdAndEntityId(
+                tenantId.getId(), customerId.getId(), entityId.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);
+    }
+
+    private DataValidator<EntityView> entityViewValidator =
+            new DataValidator<EntityView>() {
+
+                @Override
+                protected void validateCreate(EntityView entityView) {
+                    entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
+                            .ifPresent( e -> {
+                                throw new DataValidationException("Entity view with such name already exists!");
+                            });
+                }
+
+                @Override
+                protected void validateUpdate(EntityView entityView) {
+                    entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
+                            .ifPresent( e -> {
+                                if (!e.getUuidId().equals(entityView.getUuidId())) {
+                                    throw new DataValidationException("Entity view with such name already exists!");
+                                }
+                            });
+                }
+
+                @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!");
+                    }
+                    if (entityView.getTenantId() == null) {
+                        throw new DataValidationException("Entity view should be assigned to tenant!");
+                    } else {
+                        Tenant tenant = tenantDao.findById(entityView.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("Entity view is referencing to non-existent tenant!");
+                        }
+                    }
+                    if (entityView.getCustomerId() == null) {
+                        entityView.setCustomerId(new CustomerId(NULL_UUID));
+                    } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) {
+                        Customer customer = customerDao.findById(entityView.getCustomerId().getId());
+                        if (customer == null) {
+                            throw new DataValidationException("Can't assign entity view to non-existent customer!");
+                        }
+                        if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) {
+                            throw new DataValidationException("Can't assign entity view to customer from different tenant!");
+                        }
+                    }
+                }
+            };
+
+    private PaginatedRemover<TenantId, EntityView> tenantEntityViewRemover =
+            new PaginatedRemover<TenantId, EntityView>() {
+
+                @Override
+                protected List<EntityView> findEntities(TenantId id, TextPageLink pageLink) {
+                    return entityViewDao.findEntityViewByTenantId(id.getId(), pageLink);
+                }
+
+                @Override
+                protected void removeEntity(EntityView entity) {
+                    deleteEntityView(new EntityViewId(entity.getUuidId()));
+                }
+            };
+
+    private class CustomerEntityViewsUnAssigner extends PaginatedRemover<CustomerId, EntityView> {
+
+        private TenantId tenantId;
+
+        CustomerEntityViewsUnAssigner(TenantId tenantId) {
+            this.tenantId = tenantId;
+        }
+
+        @Override
+        protected List<EntityView> findEntities(CustomerId id, TextPageLink pageLink) {
+            return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
+        }
+
+        @Override
+        protected void removeEntity(EntityView entity) {
+            unassignEntityViewFromCustomer(new EntityViewId(entity.getUuidId()));
+        }
+    }
+}
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 3a934eb..d06ddbd 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,6 +45,7 @@ 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";
@@ -144,6 +145,20 @@ public class ModelConstants {
     public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant";
 
     /**
+     * Cassandra entityView constants.
+     */
+    public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views";
+    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 = "type_entity";
+    public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
+    public static final String ENTITY_VIEW_TS_BEGIN_PROPERTY = "ts_begin";
+    public static final String ENTITY_VIEW_TS_END_PROPERTY = "ts_end";
+    public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
+
+    /**
      * Cassandra audit log constants.
      */
     public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log";
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
new file mode 100644
index 0000000..65914dd
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
@@ -0,0 +1,146 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.nosql;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.hibernate.annotations.Type;
+import org.thingsboard.server.common.data.EntityView;
+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.objects.TelemetryEntityView;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.SearchTextEntity;
+
+import javax.persistence.Column;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
+
+/**
+ * Created by Victor Basanets on 8/31/2017.
+ */
+@Data
+@Table(name = ENTITY_VIEW_TABLE_FAMILY_NAME)
+@EqualsAndHashCode
+@ToString
+public class EntityViewEntity implements SearchTextEntity<EntityView> {
+
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+
+    @PartitionKey(value = 1)
+    @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
+    private UUID entityId;
+
+    @PartitionKey(value = 2)
+    @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @PartitionKey(value = 3)
+    @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
+    private UUID customerId;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
+    private String name;
+
+    @Type(type = "json")
+    @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY)
+    private JsonNode keys;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_TS_BEGIN_PROPERTY)
+    private String tsBegin;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_TS_END_PROPERTY)
+    private String tsEnd;
+
+    @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
+    private String searchText;
+
+    @Type(type = "json")
+    @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY)
+    private JsonNode additionalInfo;
+
+    public EntityViewEntity() {
+        super();
+    }
+
+    public EntityViewEntity(EntityView entityView) {
+        if (entityView.getId() != null) {
+            this.id = entityView.getId().getId();
+        }
+        if (entityView.getEntityId() != null) {
+            this.entityId = entityView.getEntityId().getId();
+        }
+        if (entityView.getTenantId() != null) {
+            this.tenantId = entityView.getTenantId().getId();
+        }
+        if (entityView.getCustomerId() != null) {
+            this.customerId = entityView.getCustomerId().getId();
+        }
+        this.name = entityView.getName();
+//        try {
+//            this.keys = entityView.getKeys();
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+        this.tsBegin = entityView.getTsBegin() != null ? String.valueOf(entityView.getTsBegin()) : "0";
+        this.tsEnd = entityView.getTsEnd() != null ? String.valueOf(entityView.getTsEnd()) : "0";
+        this.searchText = entityView.getSearchText();
+        this.additionalInfo = entityView.getAdditionalInfo();
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return name;
+    }
+
+    @Override
+    public EntityView toData() {
+        EntityView entityView = new EntityView(new EntityViewId(id));
+        entityView.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (entityId != null) {
+            entityView.setEntityId(new DeviceId(entityId));
+        }
+        if (tenantId != null) {
+            entityView.setTenantId(new TenantId(tenantId));
+        }
+        if (customerId != null) {
+            entityView.setCustomerId(new CustomerId(customerId));
+        }
+        entityView.setName(name);
+//        try {
+//            entityView.setKeys((TelemetryEntityView) entityView.getKeys().toObject(keys));
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+        entityView.setTsBegin(Long.parseLong(tsBegin));
+        entityView.setTsEnd(Long.parseLong(tsEnd));
+        entityView.setAdditionalInfo(additionalInfo);
+        return entityView;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
new file mode 100644
index 0000000..9136717
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
@@ -0,0 +1,150 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.sql;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.annotations.Type;
+import org.hibernate.annotations.TypeDef;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.id.*;
+import org.thingsboard.server.common.data.objects.TelemetryEntityView;
+import org.thingsboard.server.dao.model.BaseSqlEntity;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.SearchTextEntity;
+import org.thingsboard.server.dao.util.mapping.JsonStringType;
+
+import javax.persistence.*;
+import java.io.IOException;
+
+import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
+
+/**
+ * Created by Victor Basanets on 8/30/2017.
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Entity
+@TypeDef(name = "json", typeClass = JsonStringType.class)
+@Table(name = ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME)
+public class EntityViewEntity extends BaseSqlEntity<EntityView> implements SearchTextEntity<EntityView> {
+
+    @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
+    private String entityId;
+
+    @Enumerated(EnumType.STRING)
+    @Column(name = ENTITY_TYPE_PROPERTY)
+    private EntityType entityType;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY)
+    private String tenantId;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
+    private String customerId;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
+    private String name;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY)
+    private String keys;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_TS_BEGIN_PROPERTY)
+    private String tsBegin;
+
+    @Column(name = ModelConstants.ENTITY_VIEW_TS_END_PROPERTY)
+    private String tsEnd;
+
+    @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
+    private String searchText;
+
+    @Type(type = "json")
+    @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY)
+    private JsonNode additionalInfo;
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    public EntityViewEntity() {
+        super();
+    }
+
+    public EntityViewEntity(EntityView entityView) {
+        if (entityView.getId() != null) {
+            this.setId(entityView.getId().getId());
+        }
+        if (entityView.getEntityId() != null) {
+            this.entityId = toString(entityView.getEntityId().getId());
+            this.entityType = entityView.getEntityId().getEntityType();
+        }
+        if (entityView.getTenantId() != null) {
+            this.tenantId = toString(entityView.getTenantId().getId());
+        }
+        if (entityView.getCustomerId() != null) {
+            this.customerId = toString(entityView.getCustomerId().getId());
+        }
+        this.name = entityView.getName();
+        try {
+            this.keys = mapper.writeValueAsString(entityView.getKeys());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        this.tsBegin = entityView.getTsBegin() != null ? String.valueOf(entityView.getTsBegin()) : "0";
+        this.tsEnd = entityView.getTsEnd() != null ? String.valueOf(entityView.getTsEnd()) : "0";
+        this.searchText = entityView.getSearchText();
+        this.additionalInfo = entityView.getAdditionalInfo();
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return name;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+
+    @Override
+    public EntityView toData() {
+        EntityView entityView = new EntityView(new EntityViewId(getId()));
+        entityView.setCreatedTime(UUIDs.unixTimestamp(getId()));
+
+        if (entityId != null) {
+            entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString()));
+        }
+        if (tenantId != null) {
+            entityView.setTenantId(new TenantId(toUUID(tenantId)));
+        }
+        if (customerId != null) {
+            entityView.setCustomerId(new CustomerId(toUUID(customerId)));
+        }
+        entityView.setName(name);
+        try {
+            entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        entityView.setTsBegin(Long.parseLong(tsBegin));
+        entityView.setTsEnd(Long.parseLong(tsEnd));
+        entityView.setAdditionalInfo(additionalInfo);
+        return entityView;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
new file mode 100644
index 0000000..27bd839
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.sql.entityview;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.dao.model.sql.EntityViewEntity;
+import org.thingsboard.server.dao.util.SqlDao;
+
+import java.util.List;
+
+/**
+ * Created by Victor Basanets on 8/31/2017.
+ */
+@SqlDao
+public interface EntityViewRepository extends CrudRepository<EntityViewEntity, String> {
+
+    @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
+            "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
+            "AND e.id > :idOffset ORDER BY e.id")
+    List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
+                                      @Param("textSearch") String textSearch,
+                                      @Param("idOffset") String idOffset,
+                                      Pageable pageable);
+
+    @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
+            "AND e.entityId = :entityId " +
+            "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
+            "AND e.id > :idOffset ORDER BY e.id")
+    List<EntityViewEntity> findByTenantIdAndEntityId(@Param("tenantId") String tenantId,
+                                             @Param("entityId") String entityId,
+                                             @Param("textSearch") String textSearch,
+                                             @Param("idOffset") String idOffset,
+                                             Pageable pageable);
+
+    @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
+            "AND e.customerId = :customerId " +
+            "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
+            "AND e.id > :idOffset ORDER BY e.id")
+    List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
+                                                   @Param("customerId") String customerId,
+                                                   @Param("searchText") String searchText,
+                                                   @Param("idOffset") String idOffset,
+                                                   Pageable pageable);
+
+    @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
+            "AND e.customerId = :customerId " +
+            "AND e.entityId = :entityId " +
+            "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
+            "AND e.id > :idOffset ORDER BY e.id")
+    List<EntityViewEntity> findByTenantIdAndCustomerIdAndEntityId(@Param("tenantId") String tenantId,
+                                                          @Param("customerId") String customerId,
+                                                          @Param("entityId") String entityId,
+                                                          @Param("textSearch") String textSearch,
+                                                          @Param("idOffset") String idOffset,
+                                                          Pageable pageable);
+
+    EntityViewEntity findByTenantIdAndName(String tenantId, String name);
+
+    List<EntityViewEntity> findAllByTenantIdAndCustomerIdAndIdIn(String tenantId,
+                                                                 String customerId,
+                                                                 List<String> entityViewsIds);
+
+    List<EntityViewEntity> findAllByTenantIdAndIdIn(String tenantId, List<String> entityViewsIds);
+}
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
new file mode 100644
index 0000000..57b0afa
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.sql.entityview;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.DaoUtil;
+import org.thingsboard.server.dao.entityview.EntityViewDao;
+import org.thingsboard.server.dao.model.sql.EntityViewEntity;
+import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
+
+/**
+ * Created by Victor Basanets on 8/31/2017.
+ */
+@Component
+@SqlDao
+public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity, EntityView>
+        implements EntityViewDao {
+
+    @Autowired
+    EntityViewRepository entityViewRepository;
+
+    @Override
+    protected Class<EntityViewEntity> getEntityClass() {
+        return EntityViewEntity.class;
+    }
+
+    @Override
+    protected CrudRepository<EntityViewEntity, String> getCrudRepository() {
+        return entityViewRepository;
+    }
+
+    @Override
+    public List<EntityView> findEntityViewByTenantId(UUID tenantId, TextPageLink pageLink) {
+        return DaoUtil.convertDataList(
+                entityViewRepository.findByTenantId(
+                        fromTimeUUID(tenantId),
+                        Objects.toString(pageLink.getTextSearch(), ""),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())));
+    }
+
+    @Override
+    public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
+        return Optional.ofNullable(
+                DaoUtil.getData(entityViewRepository.findByTenantIdAndName(fromTimeUUID(tenantId), name)));
+    }
+
+    @Override
+    public List<EntityView> findEntityViewByTenantIdAndEntityId(UUID tenantId,
+                                                                UUID entityId,
+                                                                TextPageLink pageLink) {
+        return DaoUtil.convertDataList(
+                entityViewRepository.findByTenantIdAndEntityId(
+                        fromTimeUUID(tenantId),
+                        fromTimeUUID(entityId),
+                        Objects.toString(pageLink.getTextSearch(), ""),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())));
+    }
+
+    @Override
+    public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId,
+                                                                   UUID customerId,
+                                                                   TextPageLink pageLink) {
+        return DaoUtil.convertDataList(
+                entityViewRepository.findByTenantIdAndCustomerId(
+                        fromTimeUUID(tenantId),
+                        fromTimeUUID(customerId),
+                        Objects.toString(pageLink, ""),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())
+                ));
+    }
+
+    @Override
+    public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndEntityId(UUID tenantId,
+                                                                              UUID customerId,
+                                                                              UUID entityId,
+                                                                              TextPageLink pageLink) {
+        return DaoUtil.convertDataList(
+                entityViewRepository.findByTenantIdAndCustomerIdAndEntityId(
+                        fromTimeUUID(tenantId),
+                        fromTimeUUID(customerId),
+                        fromTimeUUID(entityId),
+                        Objects.toString(pageLink, ""),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())
+                ));
+    }
+}
diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql
index f03122a..bdd413d 100644
--- a/dao/src/main/resources/cassandra/schema.cql
+++ b/dao/src/main/resources/cassandra/schema.cql
@@ -638,3 +638,45 @@ CREATE TABLE IF NOT EXISTS  thingsboard.rule_node (
     additional_info text,
     PRIMARY KEY (id)
 );
+
+CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
+    id timeuuid,
+    entity_id timeuuid,
+    tenant_id timeuuid,
+    customer_id timeuuid,
+    name text,
+    keys text,
+    ts_begin bigint,
+    ts_end bigint,
+    search_text text,
+    additional_info text,
+    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 ts_begin IS NOT NULL AND ts_end 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
+    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 ts_begin IS NOT NULL AND ts_end 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);
+
+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 ts_begin IS NOT NULL AND ts_end 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);
+
+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 ts_begin IS NOT NULL AND ts_end 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);
diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql
index 91e77da..ed3583c 100644
--- a/dao/src/main/resources/sql/schema.sql
+++ b/dao/src/main/resources/sql/schema.sql
@@ -251,3 +251,17 @@ CREATE TABLE IF NOT EXISTS rule_node (
     debug_mode boolean,
     search_text varchar(255)
 );
+
+CREATE TABLE IF NOT EXISTS entity_views (
+    id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
+    entity_id varchar(31),
+    entity_type varchar(255),
+    tenant_id varchar(31),
+    customer_id varchar(31),
+    name varchar(255),
+    keys varchar(255),
+    ts_begin varchar(255),
+    ts_end varchar(255),
+    search_text varchar(255),
+    additional_info varchar
+);
diff --git a/dao/src/test/resources/sql/drop-all-tables.sql b/dao/src/test/resources/sql/drop-all-tables.sql
index 23b6a56..ebc04b3 100644
--- a/dao/src/test/resources/sql/drop-all-tables.sql
+++ b/dao/src/test/resources/sql/drop-all-tables.sql
@@ -18,4 +18,5 @@ DROP TABLE IF EXISTS user_credentials;
 DROP TABLE IF EXISTS widget_type;
 DROP TABLE IF EXISTS widgets_bundle;
 DROP TABLE IF EXISTS rule_node;
-DROP TABLE IF EXISTS rule_chain;
\ No newline at end of file
+DROP TABLE IF EXISTS rule_chain;
+DROP TABLE IF EXISTS entity_views;
\ No newline at end of file