Showing
92 changed files
with
3729 additions
and
144 deletions
Too many changes to show.
To preserve performance only 92 of 105 files are displayed.
1 | +-- | |
2 | +-- Copyright © 2016-2018 The Thingsboard Authors | |
3 | +-- | |
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +-- you may not use this file except in compliance with the License. | |
6 | +-- You may obtain a copy of the License at | |
7 | +-- | |
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +-- | |
10 | +-- Unless required by applicable law or agreed to in writing, software | |
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +-- See the License for the specific language governing permissions and | |
14 | +-- limitations under the License. | |
15 | +-- | |
16 | + | |
17 | +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name; | |
18 | +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text; | |
19 | +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer; | |
20 | +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id; | |
21 | + | |
22 | +DROP TABLE IF EXISTS thingsboard.entity_views; | |
23 | +ControllerSqlTestSuite | |
24 | +CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( | |
25 | + id timeuuid, | |
26 | + entity_id timeuuid, | |
27 | + entity_type text, | |
28 | + tenant_id timeuuid, | |
29 | + customer_id timeuuid, | |
30 | + name text, | |
31 | + keys text, | |
32 | + start_ts bigint, | |
33 | + end_ts bigint, | |
34 | + search_text text, | |
35 | + additional_info text, | |
36 | + PRIMARY KEY (id, entity_id, tenant_id, customer_id) | |
37 | +); | |
38 | + | |
39 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS | |
40 | + SELECT * | |
41 | + from thingsboard.entity_views | |
42 | + WHERE tenant_id IS NOT NULL | |
43 | + AND entity_id IS NOT NULL | |
44 | + AND customer_id IS NOT NULL | |
45 | + AND name IS NOT NULL | |
46 | + AND id IS NOT NULL | |
47 | + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id) | |
48 | + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); | |
49 | + | |
50 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS | |
51 | + SELECT * | |
52 | + from thingsboard.entity_views | |
53 | + WHERE tenant_id IS NOT NULL | |
54 | + AND entity_id IS NOT NULL | |
55 | + AND customer_id IS NOT NULL | |
56 | + AND search_text IS NOT NULL | |
57 | + AND id IS NOT NULL | |
58 | + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id) | |
59 | + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); | |
60 | + | |
61 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS | |
62 | + SELECT * | |
63 | + from thingsboard.entity_views | |
64 | + WHERE tenant_id IS NOT NULL | |
65 | + AND customer_id IS NOT NULL | |
66 | + AND entity_id IS NOT NULL | |
67 | + AND search_text IS NOT NULL | |
68 | + AND id IS NOT NULL | |
69 | + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id) | |
70 | + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); | |
71 | + | |
72 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS | |
73 | + SELECT * | |
74 | + from thingsboard.entity_views | |
75 | + WHERE tenant_id IS NOT NULL | |
76 | + AND customer_id IS NOT NULL | |
77 | + AND entity_id IS NOT NULL | |
78 | + AND search_text IS NOT NULL | |
79 | + AND id IS NOT NULL | |
80 | + PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id) | |
81 | + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); | |
\ No newline at end of file | ... | ... |
1 | +-- | |
2 | +-- Copyright © 2016-2018 The Thingsboard Authors | |
3 | +-- | |
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +-- you may not use this file except in compliance with the License. | |
6 | +-- You may obtain a copy of the License at | |
7 | +-- | |
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +-- | |
10 | +-- Unless required by applicable law or agreed to in writing, software | |
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +-- See the License for the specific language governing permissions and | |
14 | +-- limitations under the License. | |
15 | +-- | |
16 | + | |
17 | +DROP TABLE IF EXISTS entity_views; | |
18 | + | |
19 | +CREATE TABLE IF NOT EXISTS entity_views ( | |
20 | + id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY, | |
21 | + entity_id varchar(31), | |
22 | + entity_type varchar(255), | |
23 | + tenant_id varchar(31), | |
24 | + customer_id varchar(31), | |
25 | + name varchar(255), | |
26 | + keys varchar(255), | |
27 | + start_ts bigint, | |
28 | + end_ts bigint, | |
29 | + search_text varchar(255), | |
30 | + additional_info varchar | |
31 | +); | ... | ... |
... | ... | @@ -48,6 +48,7 @@ import org.thingsboard.server.dao.attributes.AttributesService; |
48 | 48 | import org.thingsboard.server.dao.audit.AuditLogService; |
49 | 49 | import org.thingsboard.server.dao.customer.CustomerService; |
50 | 50 | import org.thingsboard.server.dao.device.DeviceService; |
51 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
51 | 52 | import org.thingsboard.server.dao.event.EventService; |
52 | 53 | import org.thingsboard.server.dao.relation.RelationService; |
53 | 54 | import org.thingsboard.server.dao.rule.RuleChainService; |
... | ... | @@ -161,6 +162,10 @@ public class ActorSystemContext { |
161 | 162 | |
162 | 163 | @Autowired |
163 | 164 | @Getter |
165 | + private EntityViewService entityViewService; | |
166 | + | |
167 | + @Autowired | |
168 | + @Getter | |
164 | 169 | private TelemetrySubscriptionService tsSubService; |
165 | 170 | |
166 | 171 | @Autowired | ... | ... |
... | ... | @@ -41,6 +41,7 @@ import org.thingsboard.server.dao.asset.AssetService; |
41 | 41 | import org.thingsboard.server.dao.attributes.AttributesService; |
42 | 42 | import org.thingsboard.server.dao.customer.CustomerService; |
43 | 43 | import org.thingsboard.server.dao.device.DeviceService; |
44 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
44 | 45 | import org.thingsboard.server.dao.relation.RelationService; |
45 | 46 | import org.thingsboard.server.dao.rule.RuleChainService; |
46 | 47 | import org.thingsboard.server.dao.tenant.TenantService; |
... | ... | @@ -213,6 +214,11 @@ class DefaultTbContext implements TbContext { |
213 | 214 | } |
214 | 215 | |
215 | 216 | @Override |
217 | + public EntityViewService getEntityViewService() { | |
218 | + return mainCtx.getEntityViewService(); | |
219 | + } | |
220 | + | |
221 | + @Override | |
216 | 222 | public MailService getMailService() { |
217 | 223 | if (mainCtx.isAllowSystemMailService()) { |
218 | 224 | return mainCtx.getMailService(); | ... | ... |
... | ... | @@ -56,6 +56,7 @@ import org.thingsboard.server.dao.customer.CustomerService; |
56 | 56 | import org.thingsboard.server.dao.dashboard.DashboardService; |
57 | 57 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
58 | 58 | import org.thingsboard.server.dao.device.DeviceService; |
59 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
59 | 60 | import org.thingsboard.server.dao.exception.DataValidationException; |
60 | 61 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
61 | 62 | import org.thingsboard.server.dao.model.ModelConstants; |
... | ... | @@ -139,6 +140,9 @@ public abstract class BaseController { |
139 | 140 | @Autowired |
140 | 141 | protected DeviceStateService deviceStateService; |
141 | 142 | |
143 | + @Autowired | |
144 | + protected EntityViewService entityViewService; | |
145 | + | |
142 | 146 | @ExceptionHandler(ThingsboardException.class) |
143 | 147 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { |
144 | 148 | errorResponseHandler.handle(ex, response); |
... | ... | @@ -313,6 +317,9 @@ public abstract class BaseController { |
313 | 317 | case USER: |
314 | 318 | checkUserId(new UserId(entityId.getId())); |
315 | 319 | return; |
320 | + case ENTITY_VIEW: | |
321 | + checkEntityViewId(new EntityViewId(entityId.getId())); | |
322 | + return; | |
316 | 323 | default: |
317 | 324 | throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); |
318 | 325 | } |
... | ... | @@ -340,6 +347,25 @@ public abstract class BaseController { |
340 | 347 | } |
341 | 348 | } |
342 | 349 | |
350 | + protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException { | |
351 | + try { | |
352 | + validateId(entityViewId, "Incorrect entityViewId " + entityViewId); | |
353 | + EntityView entityView = entityViewService.findEntityViewById(entityViewId); | |
354 | + checkEntityView(entityView); | |
355 | + return entityView; | |
356 | + } catch (Exception e) { | |
357 | + throw handleException(e, false); | |
358 | + } | |
359 | + } | |
360 | + | |
361 | + protected void checkEntityView(EntityView entityView) throws ThingsboardException { | |
362 | + checkNotNull(entityView); | |
363 | + checkTenantId(entityView.getTenantId()); | |
364 | + if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | |
365 | + checkCustomerId(entityView.getCustomerId()); | |
366 | + } | |
367 | + } | |
368 | + | |
343 | 369 | Asset checkAssetId(AssetId assetId) throws ThingsboardException { |
344 | 370 | try { |
345 | 371 | validateId(assetId, "Incorrect assetId " + assetId); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +import org.springframework.http.HttpStatus; | |
19 | +import org.springframework.security.access.prepost.PreAuthorize; | |
20 | +import org.springframework.web.bind.annotation.PathVariable; | |
21 | +import org.springframework.web.bind.annotation.RequestBody; | |
22 | +import org.springframework.web.bind.annotation.RequestMapping; | |
23 | +import org.springframework.web.bind.annotation.RequestMethod; | |
24 | +import org.springframework.web.bind.annotation.RequestParam; | |
25 | +import org.springframework.web.bind.annotation.ResponseBody; | |
26 | +import org.springframework.web.bind.annotation.ResponseStatus; | |
27 | +import org.springframework.web.bind.annotation.RestController; | |
28 | +import org.thingsboard.server.common.data.Customer; | |
29 | +import org.thingsboard.server.common.data.EntityType; | |
30 | +import org.thingsboard.server.common.data.EntityView; | |
31 | +import org.thingsboard.server.common.data.audit.ActionType; | |
32 | +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; | |
33 | +import org.thingsboard.server.common.data.exception.ThingsboardException; | |
34 | +import org.thingsboard.server.common.data.id.CustomerId; | |
35 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
36 | +import org.thingsboard.server.common.data.id.TenantId; | |
37 | +import org.thingsboard.server.common.data.page.TextPageData; | |
38 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
39 | +import org.thingsboard.server.dao.exception.IncorrectParameterException; | |
40 | +import org.thingsboard.server.dao.model.ModelConstants; | |
41 | + | |
42 | +import java.util.List; | |
43 | +import java.util.stream.Collectors; | |
44 | + | |
45 | +import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; | |
46 | + | |
47 | +/** | |
48 | + * Created by Victor Basanets on 8/28/2017. | |
49 | + */ | |
50 | +@RestController | |
51 | +@RequestMapping("/api") | |
52 | +public class EntityViewController extends BaseController { | |
53 | + | |
54 | + public static final String ENTITY_VIEW_ID = "entityViewId"; | |
55 | + | |
56 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
57 | + @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET) | |
58 | + @ResponseBody | |
59 | + public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | |
60 | + checkParameter(ENTITY_VIEW_ID, strEntityViewId); | |
61 | + try { | |
62 | + return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId))); | |
63 | + } catch (Exception e) { | |
64 | + throw handleException(e); | |
65 | + } | |
66 | + } | |
67 | + | |
68 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
69 | + @RequestMapping(value = "/entityView", method = RequestMethod.POST) | |
70 | + @ResponseBody | |
71 | + public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException { | |
72 | + try { | |
73 | + entityView.setTenantId(getCurrentUser().getTenantId()); | |
74 | + EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); | |
75 | + logEntityAction(savedEntityView.getId(), savedEntityView, null, | |
76 | + entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | |
77 | + return savedEntityView; | |
78 | + } catch (Exception e) { | |
79 | + logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null, | |
80 | + entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | |
81 | + throw handleException(e); | |
82 | + } | |
83 | + } | |
84 | + | |
85 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
86 | + @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE) | |
87 | + @ResponseStatus(value = HttpStatus.OK) | |
88 | + public void deleteEntityView(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | |
89 | + checkParameter(ENTITY_VIEW_ID, strEntityViewId); | |
90 | + try { | |
91 | + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); | |
92 | + EntityView entityView = checkEntityViewId(entityViewId); | |
93 | + entityViewService.deleteEntityView(entityViewId); | |
94 | + logEntityAction(entityViewId, entityView, entityView.getCustomerId(), | |
95 | + ActionType.DELETED,null, strEntityViewId); | |
96 | + } catch (Exception e) { | |
97 | + logEntityAction(emptyId(EntityType.ENTITY_VIEW), | |
98 | + null, | |
99 | + null, | |
100 | + ActionType.DELETED, e, strEntityViewId); | |
101 | + throw handleException(e); | |
102 | + } | |
103 | + } | |
104 | + | |
105 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
106 | + @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) | |
107 | + @ResponseBody | |
108 | + public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId, | |
109 | + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | |
110 | + checkParameter(CUSTOMER_ID, strCustomerId); | |
111 | + checkParameter(ENTITY_VIEW_ID, strEntityViewId); | |
112 | + try { | |
113 | + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | |
114 | + Customer customer = checkCustomerId(customerId); | |
115 | + | |
116 | + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); | |
117 | + checkEntityViewId(entityViewId); | |
118 | + | |
119 | + EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId)); | |
120 | + logEntityAction(entityViewId, savedEntityView, | |
121 | + savedEntityView.getCustomerId(), | |
122 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName()); | |
123 | + return savedEntityView; | |
124 | + } catch (Exception e) { | |
125 | + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, | |
126 | + null, | |
127 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId); | |
128 | + throw handleException(e); | |
129 | + } | |
130 | + } | |
131 | + | |
132 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
133 | + @RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE) | |
134 | + @ResponseBody | |
135 | + public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { | |
136 | + checkParameter(ENTITY_VIEW_ID, strEntityViewId); | |
137 | + try { | |
138 | + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); | |
139 | + EntityView entityView = checkEntityViewId(entityViewId); | |
140 | + if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | |
141 | + throw new IncorrectParameterException("Entity View isn't assigned to any customer!"); | |
142 | + } | |
143 | + Customer customer = checkCustomerId(entityView.getCustomerId()); | |
144 | + EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId)); | |
145 | + logEntityAction(entityViewId, entityView, | |
146 | + entityView.getCustomerId(), | |
147 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName()); | |
148 | + | |
149 | + return savedEntityView; | |
150 | + } catch (Exception e) { | |
151 | + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, | |
152 | + null, | |
153 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId); | |
154 | + throw handleException(e); | |
155 | + } | |
156 | + } | |
157 | + | |
158 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
159 | + @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET) | |
160 | + @ResponseBody | |
161 | + public TextPageData<EntityView> getCustomerEntityViews( | |
162 | + @PathVariable("customerId") String strCustomerId, | |
163 | + @RequestParam int limit, | |
164 | + @RequestParam(required = false) String textSearch, | |
165 | + @RequestParam(required = false) String idOffset, | |
166 | + @RequestParam(required = false) String textOffset) throws ThingsboardException { | |
167 | + checkParameter("customerId", strCustomerId); | |
168 | + try { | |
169 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
170 | + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | |
171 | + checkCustomerId(customerId); | |
172 | + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); | |
173 | + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); | |
174 | + } catch (Exception e) { | |
175 | + throw handleException(e); | |
176 | + } | |
177 | + } | |
178 | + | |
179 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
180 | + @RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET) | |
181 | + @ResponseBody | |
182 | + public TextPageData<EntityView> getTenantEntityViews( | |
183 | + @RequestParam int limit, | |
184 | + @RequestParam(required = false) String textSearch, | |
185 | + @RequestParam(required = false) String idOffset, | |
186 | + @RequestParam(required = false) String textOffset) throws ThingsboardException { | |
187 | + try { | |
188 | + TenantId tenantId = getCurrentUser().getTenantId(); | |
189 | + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); | |
190 | + return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink)); | |
191 | + } catch (Exception e) { | |
192 | + throw handleException(e); | |
193 | + } | |
194 | + } | |
195 | + | |
196 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
197 | + @RequestMapping(value = "/entityViews", method = RequestMethod.POST) | |
198 | + @ResponseBody | |
199 | + public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException { | |
200 | + checkNotNull(query); | |
201 | + checkNotNull(query.getParameters()); | |
202 | + checkEntityId(query.getParameters().getEntityId()); | |
203 | + try { | |
204 | + List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get()); | |
205 | + entityViews = entityViews.stream().filter(entityView -> { | |
206 | + try { | |
207 | + checkEntityView(entityView); | |
208 | + return true; | |
209 | + } catch (ThingsboardException e) { | |
210 | + return false; | |
211 | + } | |
212 | + }).collect(Collectors.toList()); | |
213 | + return entityViews; | |
214 | + } catch (Exception e) { | |
215 | + throw handleException(e); | |
216 | + } | |
217 | + } | |
218 | +} | ... | ... |
... | ... | @@ -24,9 +24,10 @@ import org.springframework.context.annotation.Profile; |
24 | 24 | import org.springframework.stereotype.Service; |
25 | 25 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
26 | 26 | import org.thingsboard.server.service.install.DataUpdateService; |
27 | -import org.thingsboard.server.service.install.DatabaseSchemaService; | |
28 | 27 | import org.thingsboard.server.service.install.DatabaseUpgradeService; |
28 | +import org.thingsboard.server.service.install.EntityDatabaseSchemaService; | |
29 | 29 | import org.thingsboard.server.service.install.SystemDataLoaderService; |
30 | +import org.thingsboard.server.service.install.TsDatabaseSchemaService; | |
30 | 31 | |
31 | 32 | @Service |
32 | 33 | @Profile("install") |
... | ... | @@ -43,7 +44,10 @@ public class ThingsboardInstallService { |
43 | 44 | private Boolean loadDemo; |
44 | 45 | |
45 | 46 | @Autowired |
46 | - private DatabaseSchemaService databaseSchemaService; | |
47 | + private EntityDatabaseSchemaService entityDatabaseSchemaService; | |
48 | + | |
49 | + @Autowired | |
50 | + private TsDatabaseSchemaService tsDatabaseSchemaService; | |
47 | 51 | |
48 | 52 | @Autowired |
49 | 53 | private DatabaseUpgradeService databaseUpgradeService; |
... | ... | @@ -88,6 +92,11 @@ public class ThingsboardInstallService { |
88 | 92 | |
89 | 93 | dataUpdateService.updateData("1.4.0"); |
90 | 94 | |
95 | + case "2.0.0": | |
96 | + log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ..."); | |
97 | + | |
98 | + databaseUpgradeService.upgradeDatabase("2.0.0"); | |
99 | + | |
91 | 100 | log.info("Updating system data..."); |
92 | 101 | |
93 | 102 | systemDataLoaderService.deleteSystemWidgetBundle("charts"); |
... | ... | @@ -114,9 +123,13 @@ public class ThingsboardInstallService { |
114 | 123 | |
115 | 124 | log.info("Starting ThingsBoard Installation..."); |
116 | 125 | |
117 | - log.info("Installing DataBase schema..."); | |
126 | + log.info("Installing DataBase schema for entities..."); | |
127 | + | |
128 | + entityDatabaseSchemaService.createDatabaseSchema(); | |
129 | + | |
130 | + log.info("Installing DataBase schema for timeseries..."); | |
118 | 131 | |
119 | - databaseSchemaService.createDatabaseSchema(); | |
132 | + tsDatabaseSchemaService.createDatabaseSchema(); | |
120 | 133 | |
121 | 134 | log.info("Loading system data..."); |
122 | 135 | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java
renamed from
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
... | ... | @@ -17,24 +17,17 @@ package org.thingsboard.server.service.install; |
17 | 17 | |
18 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.springframework.beans.factory.annotation.Autowired; |
20 | -import org.springframework.context.annotation.Profile; | |
21 | -import org.springframework.stereotype.Service; | |
22 | 20 | import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; |
23 | -import org.thingsboard.server.dao.util.NoSqlDao; | |
24 | 21 | import org.thingsboard.server.service.install.cql.CQLStatementsParser; |
25 | 22 | |
26 | 23 | import java.nio.file.Path; |
27 | 24 | import java.nio.file.Paths; |
28 | 25 | import java.util.List; |
29 | 26 | |
30 | -@Service | |
31 | -@NoSqlDao | |
32 | -@Profile("install") | |
33 | 27 | @Slf4j |
34 | -public class CassandraDatabaseSchemaService implements DatabaseSchemaService { | |
28 | +public abstract class CassandraAbstractDatabaseSchemaService implements DatabaseSchemaService { | |
35 | 29 | |
36 | 30 | private static final String CASSANDRA_DIR = "cassandra"; |
37 | - private static final String SCHEMA_CQL = "schema.cql"; | |
38 | 31 | |
39 | 32 | @Autowired |
40 | 33 | private CassandraInstallCluster cluster; |
... | ... | @@ -42,10 +35,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService { |
42 | 35 | @Autowired |
43 | 36 | private InstallScripts installScripts; |
44 | 37 | |
38 | + private final String schemaCql; | |
39 | + | |
40 | + protected CassandraAbstractDatabaseSchemaService(String schemaCql) { | |
41 | + this.schemaCql = schemaCql; | |
42 | + } | |
43 | + | |
45 | 44 | @Override |
46 | 45 | public void createDatabaseSchema() throws Exception { |
47 | - log.info("Installing Cassandra DataBase schema..."); | |
48 | - Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL); | |
46 | + log.info("Installing Cassandra DataBase schema part: " + schemaCql); | |
47 | + Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, schemaCql); | |
49 | 48 | loadCql(schemaFile); |
50 | 49 | |
51 | 50 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install; | |
17 | + | |
18 | +import org.springframework.context.annotation.Profile; | |
19 | +import org.springframework.stereotype.Service; | |
20 | +import org.thingsboard.server.dao.util.NoSqlDao; | |
21 | + | |
22 | +@Service | |
23 | +@NoSqlDao | |
24 | +@Profile("install") | |
25 | +public class CassandraEntityDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService | |
26 | + implements EntityDatabaseSchemaService { | |
27 | + public CassandraEntityDatabaseSchemaService() { | |
28 | + super("schema-entities.cql"); | |
29 | + } | |
30 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install; | |
17 | + | |
18 | +import org.springframework.context.annotation.Profile; | |
19 | +import org.springframework.stereotype.Service; | |
20 | +import org.thingsboard.server.dao.util.NoSqlTsDao; | |
21 | + | |
22 | +@Service | |
23 | +@NoSqlTsDao | |
24 | +@Profile("install") | |
25 | +public class CassandraTsDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService | |
26 | + implements TsDatabaseSchemaService { | |
27 | + public CassandraTsDatabaseSchemaService() { | |
28 | + super("schema-ts.cql"); | |
29 | + } | |
30 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install; | |
17 | + | |
18 | +public interface EntityDatabaseSchemaService extends DatabaseSchemaService { | |
19 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java
renamed from
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
... | ... | @@ -18,9 +18,6 @@ package org.thingsboard.server.service.install; |
18 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.springframework.beans.factory.annotation.Autowired; |
20 | 20 | import org.springframework.beans.factory.annotation.Value; |
21 | -import org.springframework.context.annotation.Profile; | |
22 | -import org.springframework.stereotype.Service; | |
23 | -import org.thingsboard.server.dao.util.SqlDao; | |
24 | 21 | |
25 | 22 | import java.nio.charset.Charset; |
26 | 23 | import java.nio.file.Files; |
... | ... | @@ -29,14 +26,10 @@ import java.nio.file.Paths; |
29 | 26 | import java.sql.Connection; |
30 | 27 | import java.sql.DriverManager; |
31 | 28 | |
32 | -@Service | |
33 | -@Profile("install") | |
34 | 29 | @Slf4j |
35 | -@SqlDao | |
36 | -public class SqlDatabaseSchemaService implements DatabaseSchemaService { | |
30 | +public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService { | |
37 | 31 | |
38 | 32 | private static final String SQL_DIR = "sql"; |
39 | - private static final String SCHEMA_SQL = "schema.sql"; | |
40 | 33 | |
41 | 34 | @Value("${spring.datasource.url}") |
42 | 35 | private String dbUrl; |
... | ... | @@ -50,12 +43,18 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService { |
50 | 43 | @Autowired |
51 | 44 | private InstallScripts installScripts; |
52 | 45 | |
46 | + private final String schemaSql; | |
47 | + | |
48 | + protected SqlAbstractDatabaseSchemaService(String schemaSql) { | |
49 | + this.schemaSql = schemaSql; | |
50 | + } | |
51 | + | |
53 | 52 | @Override |
54 | 53 | public void createDatabaseSchema() throws Exception { |
55 | 54 | |
56 | - log.info("Installing SQL DataBase schema..."); | |
55 | + log.info("Installing SQL DataBase schema part: " + schemaSql); | |
57 | 56 | |
58 | - Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL); | |
57 | + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaSql); | |
59 | 58 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
60 | 59 | String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8")); |
61 | 60 | conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema | ... | ... |
... | ... | @@ -107,6 +107,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { |
107 | 107 | log.info("Schema updated."); |
108 | 108 | } |
109 | 109 | break; |
110 | + case "2.0.0": | |
111 | + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | |
112 | + log.info("Updating schema ..."); | |
113 | + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_SQL); | |
114 | + loadSql(schemaUpdateFile, conn); | |
115 | + log.info("Schema updated."); | |
116 | + } | |
117 | + break; | |
118 | + | |
110 | 119 | default: |
111 | 120 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
112 | 121 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install; | |
17 | + | |
18 | +import org.springframework.context.annotation.Profile; | |
19 | +import org.springframework.stereotype.Service; | |
20 | +import org.thingsboard.server.dao.util.SqlDao; | |
21 | + | |
22 | +@Service | |
23 | +@SqlDao | |
24 | +@Profile("install") | |
25 | +public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService | |
26 | + implements EntityDatabaseSchemaService { | |
27 | + public SqlEntityDatabaseSchemaService() { | |
28 | + super("schema-entities.sql"); | |
29 | + } | |
30 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install; | |
17 | + | |
18 | +import org.springframework.context.annotation.Profile; | |
19 | +import org.springframework.stereotype.Service; | |
20 | +import org.thingsboard.server.dao.util.SqlTsDao; | |
21 | + | |
22 | +@Service | |
23 | +@SqlTsDao | |
24 | +@Profile("install") | |
25 | +public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService | |
26 | + implements TsDatabaseSchemaService { | |
27 | + public SqlTsDatabaseSchemaService() { | |
28 | + super("schema-ts.sql"); | |
29 | + } | |
30 | +} | |
\ No newline at end of file | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install; | |
17 | + | |
18 | +public interface TsDatabaseSchemaService extends DatabaseSchemaService { | |
19 | +} | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.springframework.stereotype.Component; |
26 | 26 | import org.springframework.web.context.request.async.DeferredResult; |
27 | 27 | import org.thingsboard.server.common.data.Customer; |
28 | 28 | import org.thingsboard.server.common.data.Device; |
29 | +import org.thingsboard.server.common.data.EntityView; | |
29 | 30 | import org.thingsboard.server.common.data.Tenant; |
30 | 31 | import org.thingsboard.server.common.data.asset.Asset; |
31 | 32 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
... | ... | @@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.id.CustomerId; |
34 | 35 | import org.thingsboard.server.common.data.id.DeviceId; |
35 | 36 | import org.thingsboard.server.common.data.id.EntityId; |
36 | 37 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
38 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
37 | 39 | import org.thingsboard.server.common.data.id.RuleChainId; |
38 | 40 | import org.thingsboard.server.common.data.id.RuleNodeId; |
39 | 41 | import org.thingsboard.server.common.data.id.TenantId; |
... | ... | @@ -44,6 +46,7 @@ import org.thingsboard.server.dao.alarm.AlarmService; |
44 | 46 | import org.thingsboard.server.dao.asset.AssetService; |
45 | 47 | import org.thingsboard.server.dao.customer.CustomerService; |
46 | 48 | import org.thingsboard.server.dao.device.DeviceService; |
49 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
47 | 50 | import org.thingsboard.server.dao.rule.RuleChainService; |
48 | 51 | import org.thingsboard.server.dao.tenant.TenantService; |
49 | 52 | import org.thingsboard.server.dao.user.UserService; |
... | ... | @@ -66,6 +69,7 @@ public class AccessValidator { |
66 | 69 | public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; |
67 | 70 | public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; |
68 | 71 | public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; |
72 | + public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!"; | |
69 | 73 | |
70 | 74 | @Autowired |
71 | 75 | protected TenantService tenantService; |
... | ... | @@ -88,6 +92,9 @@ public class AccessValidator { |
88 | 92 | @Autowired |
89 | 93 | protected RuleChainService ruleChainService; |
90 | 94 | |
95 | + @Autowired | |
96 | + protected EntityViewService entityViewService; | |
97 | + | |
91 | 98 | private ExecutorService executor; |
92 | 99 | |
93 | 100 | @PostConstruct |
... | ... | @@ -158,6 +165,9 @@ public class AccessValidator { |
158 | 165 | case TENANT: |
159 | 166 | validateTenant(currentUser, entityId, callback); |
160 | 167 | return; |
168 | + case ENTITY_VIEW: | |
169 | + validateEntityView(currentUser, entityId, callback); | |
170 | + return; | |
161 | 171 | default: |
162 | 172 | //TODO: add support of other entities |
163 | 173 | throw new IllegalStateException("Not Implemented!"); |
... | ... | @@ -293,6 +303,27 @@ public class AccessValidator { |
293 | 303 | } |
294 | 304 | } |
295 | 305 | |
306 | + private void validateEntityView(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) { | |
307 | + if (currentUser.isSystemAdmin()) { | |
308 | + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); | |
309 | + } else { | |
310 | + ListenableFuture<EntityView> entityViewFuture = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId())); | |
311 | + Futures.addCallback(entityViewFuture, getCallback(callback, entityView -> { | |
312 | + if (entityView == null) { | |
313 | + return ValidationResult.entityNotFound(ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND); | |
314 | + } else { | |
315 | + if (!entityView.getTenantId().equals(currentUser.getTenantId())) { | |
316 | + return ValidationResult.accessDenied("Entity-view doesn't belong to the current Tenant!"); | |
317 | + } else if (currentUser.isCustomerUser() && !entityView.getCustomerId().equals(currentUser.getCustomerId())) { | |
318 | + return ValidationResult.accessDenied("Entity-view doesn't belong to the current Customer!"); | |
319 | + } else { | |
320 | + return ValidationResult.ok(entityView); | |
321 | + } | |
322 | + } | |
323 | + }), executor); | |
324 | + } | |
325 | + } | |
326 | + | |
296 | 327 | private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) { |
297 | 328 | return new FutureCallback<T>() { |
298 | 329 | @Override | ... | ... |
... | ... | @@ -29,9 +29,11 @@ import org.thingsboard.rule.engine.api.util.DonAsynchron; |
29 | 29 | import org.thingsboard.server.actors.service.ActorService; |
30 | 30 | import org.thingsboard.server.common.data.DataConstants; |
31 | 31 | import org.thingsboard.server.common.data.EntityType; |
32 | +import org.thingsboard.server.common.data.EntityView; | |
32 | 33 | import org.thingsboard.server.common.data.id.DeviceId; |
33 | 34 | import org.thingsboard.server.common.data.id.EntityId; |
34 | 35 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
36 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
35 | 37 | import org.thingsboard.server.common.data.id.TenantId; |
36 | 38 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
37 | 39 | import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
... | ... | @@ -48,6 +50,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; |
48 | 50 | import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
49 | 51 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
50 | 52 | import org.thingsboard.server.dao.attributes.AttributesService; |
53 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
54 | +import org.thingsboard.server.dao.model.ModelConstants; | |
51 | 55 | import org.thingsboard.server.dao.timeseries.TimeseriesService; |
52 | 56 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
53 | 57 | import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
... | ... | @@ -64,6 +68,7 @@ import javax.annotation.PostConstruct; |
64 | 68 | import javax.annotation.PreDestroy; |
65 | 69 | import java.util.ArrayList; |
66 | 70 | import java.util.Collections; |
71 | +import java.util.HashMap; | |
67 | 72 | import java.util.HashSet; |
68 | 73 | import java.util.Iterator; |
69 | 74 | import java.util.List; |
... | ... | @@ -102,6 +107,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
102 | 107 | private ClusterRpcService rpcService; |
103 | 108 | |
104 | 109 | @Autowired |
110 | + private EntityViewService entityViewService; | |
111 | + | |
112 | + @Autowired | |
105 | 113 | @Lazy |
106 | 114 | private DeviceStateService stateService; |
107 | 115 | |
... | ... | @@ -133,20 +141,64 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
133 | 141 | |
134 | 142 | @Override |
135 | 143 | public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) { |
144 | + long startTime = 0L; | |
145 | + long endTime = 0L; | |
146 | + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { | |
147 | + EntityView entityView = entityViewService.findEntityViewById(new EntityViewId(entityId.getId())); | |
148 | + entityId = entityView.getEntityId(); | |
149 | + startTime = entityView.getStartTimeMs(); | |
150 | + endTime = entityView.getEndTimeMs(); | |
151 | + sub = getUpdatedSubscriptionState(entityId, sub, entityView); | |
152 | + } | |
136 | 153 | Optional<ServerAddress> server = routingService.resolveById(entityId); |
137 | 154 | Subscription subscription; |
138 | 155 | if (server.isPresent()) { |
139 | 156 | ServerAddress address = server.get(); |
140 | - log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address); | |
141 | - subscription = new Subscription(sub, true, address); | |
157 | + log.trace("[{}] Forwarding subscription [{}] for [{}] entity [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId, address); | |
158 | + subscription = new Subscription(sub, true, address, startTime, endTime); | |
142 | 159 | tellNewSubscription(address, sessionId, subscription); |
143 | 160 | } else { |
144 | - log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId); | |
145 | - subscription = new Subscription(sub, true); | |
161 | + log.trace("[{}] Registering local subscription [{}] for [{}] entity [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId); | |
162 | + subscription = new Subscription(sub, true, null, startTime, endTime); | |
146 | 163 | } |
147 | 164 | registerSubscription(sessionId, entityId, subscription); |
148 | 165 | } |
149 | 166 | |
167 | + private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) { | |
168 | + boolean allKeys; | |
169 | + Map<String, Long> keyStates; | |
170 | + if (sub.getType().equals(TelemetryFeature.TIMESERIES) && !entityView.getKeys().getTimeseries().isEmpty()) { | |
171 | + allKeys = false; | |
172 | + keyStates = sub.getKeyStates().entrySet() | |
173 | + .stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey())) | |
174 | + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | |
175 | + } else if (sub.getType().equals(TelemetryFeature.ATTRIBUTES)) { | |
176 | + if (sub.getScope().equals(DataConstants.CLIENT_SCOPE) && !entityView.getKeys().getAttributes().getCs().isEmpty()) { | |
177 | + allKeys = false; | |
178 | + keyStates = filterMap(sub, entityView.getKeys().getAttributes().getCs()); | |
179 | + } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSs().isEmpty()) { | |
180 | + allKeys = false; | |
181 | + keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSs()); | |
182 | + } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSh().isEmpty()) { | |
183 | + allKeys = false; | |
184 | + keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSh()); | |
185 | + } else { | |
186 | + allKeys = sub.isAllKeys(); | |
187 | + keyStates = sub.getKeyStates(); | |
188 | + } | |
189 | + } else { | |
190 | + allKeys = sub.isAllKeys(); | |
191 | + keyStates = sub.getKeyStates(); | |
192 | + } | |
193 | + return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), allKeys, keyStates, sub.getScope()); | |
194 | + } | |
195 | + | |
196 | + private Map<String, Long> filterMap(SubscriptionState sub, List<String> allowedKeys) { | |
197 | + return sub.getKeyStates().entrySet() | |
198 | + .stream().filter(entry -> allowedKeys.contains(entry.getKey())) | |
199 | + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | |
200 | + } | |
201 | + | |
150 | 202 | @Override |
151 | 203 | public void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId) { |
152 | 204 | cleanupLocalWsSessionSubscriptions(sessionId); |
... | ... | @@ -415,7 +467,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
415 | 467 | onLocalSubUpdate(entityId, s -> TelemetryFeature.ATTRIBUTES == s.getType() && (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope())), s -> { |
416 | 468 | List<TsKvEntry> subscriptionUpdate = null; |
417 | 469 | for (AttributeKvEntry kv : attributes) { |
418 | - if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) { | |
470 | + if (isInTimeRange(s, kv.getLastUpdateTs()) && (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey()))) { | |
419 | 471 | if (subscriptionUpdate == null) { |
420 | 472 | subscriptionUpdate = new ArrayList<>(); |
421 | 473 | } |
... | ... | @@ -430,7 +482,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
430 | 482 | onLocalSubUpdate(entityId, s -> TelemetryFeature.TIMESERIES == s.getType(), s -> { |
431 | 483 | List<TsKvEntry> subscriptionUpdate = null; |
432 | 484 | for (TsKvEntry kv : ts) { |
433 | - if (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey()))) { | |
485 | + if (isInTimeRange(s, kv.getTs()) && (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) { | |
434 | 486 | if (subscriptionUpdate == null) { |
435 | 487 | subscriptionUpdate = new ArrayList<>(); |
436 | 488 | } |
... | ... | @@ -441,6 +493,11 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio |
441 | 493 | }); |
442 | 494 | } |
443 | 495 | |
496 | + private boolean isInTimeRange(Subscription subscription, long kvTime) { | |
497 | + return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime) | |
498 | + && (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime); | |
499 | + } | |
500 | + | |
444 | 501 | private void onLocalSubUpdate(EntityId entityId, Predicate<Subscription> filter, Function<Subscription, List<TsKvEntry>> f) { |
445 | 502 | Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId); |
446 | 503 | if (deviceSubscriptions != null) { | ... | ... |
... | ... | @@ -30,9 +30,11 @@ public class Subscription { |
30 | 30 | private final SubscriptionState sub; |
31 | 31 | private final boolean local; |
32 | 32 | private ServerAddress server; |
33 | + private long startTime; | |
34 | + private long endTime; | |
33 | 35 | |
34 | - public Subscription(SubscriptionState sub, boolean local) { | |
35 | - this(sub, local, null); | |
36 | + public Subscription(SubscriptionState sub, boolean local, ServerAddress server) { | |
37 | + this(sub, local, server, 0L, 0L); | |
36 | 38 | } |
37 | 39 | |
38 | 40 | public String getWsSessionId() { | ... | ... |
... | ... | @@ -159,7 +159,11 @@ quota: |
159 | 159 | intervalMin: 2 |
160 | 160 | |
161 | 161 | database: |
162 | - type: "${DATABASE_TYPE:sql}" # cassandra OR sql | |
162 | + entities: | |
163 | + type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql | |
164 | + ts: | |
165 | + type: "${DATABASE_TS_TYPE:sql}" # cassandra OR sql (for hybrid mode, only this value should be cassandra) | |
166 | + | |
163 | 167 | |
164 | 168 | # Cassandra driver configuration parameters |
165 | 169 | cassandra: |
... | ... | @@ -206,7 +210,7 @@ cassandra: |
206 | 210 | write_consistency_level: "${CASSANDRA_WRITE_CONSISTENCY_LEVEL:ONE}" |
207 | 211 | default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}" |
208 | 212 | # Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS,INDEFINITE |
209 | - ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" | |
213 | + ts_key_value_partitioning: "${TS_KV_PARTITIONING:INDEFINITE}" | |
210 | 214 | ts_key_value_ttl: "${TS_KV_TTL:0}" |
211 | 215 | buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}" |
212 | 216 | concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}" |
... | ... | @@ -289,6 +293,9 @@ caffeine: |
289 | 293 | assets: |
290 | 294 | timeToLiveInMinutes: 1440 |
291 | 295 | maxSize: 100000 |
296 | + entityViews: | |
297 | + timeToLiveInMinutes: 1440 | |
298 | + maxSize: 100000 | |
292 | 299 | |
293 | 300 | redis: |
294 | 301 | # standalone or cluster |
... | ... | @@ -350,7 +357,7 @@ spring: |
350 | 357 | password: "${SPRING_DATASOURCE_PASSWORD:}" |
351 | 358 | |
352 | 359 | # PostgreSQL DAO Configuration |
353 | -#spring: | |
360 | +# spring: | |
354 | 361 | # data: |
355 | 362 | # sql: |
356 | 363 | # repositories: | ... | ... |
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.fasterxml.jackson.core.type.TypeReference; | |
20 | +import org.apache.commons.lang3.RandomStringUtils; | |
21 | +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; | |
22 | +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; | |
23 | +import org.eclipse.paho.client.mqttv3.MqttMessage; | |
24 | +import org.junit.After; | |
25 | +import org.junit.Assert; | |
26 | +import org.junit.Before; | |
27 | +import org.junit.Test; | |
28 | +import org.thingsboard.server.common.data.Customer; | |
29 | +import org.thingsboard.server.common.data.Device; | |
30 | +import org.thingsboard.server.common.data.EntityView; | |
31 | +import org.thingsboard.server.common.data.Tenant; | |
32 | +import org.thingsboard.server.common.data.User; | |
33 | +import org.thingsboard.server.common.data.id.CustomerId; | |
34 | +import org.thingsboard.server.common.data.objects.AttributesEntityView; | |
35 | +import org.thingsboard.server.common.data.objects.TelemetryEntityView; | |
36 | +import org.thingsboard.server.common.data.page.TextPageData; | |
37 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
38 | +import org.thingsboard.server.common.data.security.Authority; | |
39 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | |
40 | +import org.thingsboard.server.dao.model.ModelConstants; | |
41 | + | |
42 | +import java.util.ArrayList; | |
43 | +import java.util.Arrays; | |
44 | +import java.util.Collections; | |
45 | +import java.util.HashSet; | |
46 | +import java.util.List; | |
47 | +import java.util.Map; | |
48 | +import java.util.Set; | |
49 | + | |
50 | +import static org.hamcrest.Matchers.containsString; | |
51 | +import static org.junit.Assert.assertEquals; | |
52 | +import static org.junit.Assert.assertNotNull; | |
53 | +import static org.junit.Assert.assertNull; | |
54 | +import static org.junit.Assert.assertTrue; | |
55 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | |
56 | +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; | |
57 | + | |
58 | +public abstract class BaseEntityViewControllerTest extends AbstractControllerTest { | |
59 | + | |
60 | + private IdComparator<EntityView> idComparator; | |
61 | + private Tenant savedTenant; | |
62 | + private User tenantAdmin; | |
63 | + private Device testDevice; | |
64 | + private TelemetryEntityView telemetry; | |
65 | + | |
66 | + @Before | |
67 | + public void beforeTest() throws Exception { | |
68 | + loginSysAdmin(); | |
69 | + idComparator = new IdComparator<>(); | |
70 | + | |
71 | + savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class); | |
72 | + Assert.assertNotNull(savedTenant); | |
73 | + | |
74 | + tenantAdmin = new User(); | |
75 | + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); | |
76 | + tenantAdmin.setTenantId(savedTenant.getId()); | |
77 | + tenantAdmin.setEmail("tenant2@thingsboard.org"); | |
78 | + tenantAdmin.setFirstName("Joe"); | |
79 | + tenantAdmin.setLastName("Downs"); | |
80 | + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); | |
81 | + | |
82 | + Device device = new Device(); | |
83 | + device.setName("Test device"); | |
84 | + device.setType("default"); | |
85 | + testDevice = doPost("/api/device", device, Device.class); | |
86 | + | |
87 | + telemetry = new TelemetryEntityView( | |
88 | + Arrays.asList("109", "209", "309"), | |
89 | + new AttributesEntityView( | |
90 | + Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"), | |
91 | + Arrays.asList("saValue1", "saValue2", "saValue3", "saValue4"), | |
92 | + Arrays.asList("shValue1", "shValue2", "shValue3", "shValue4"))); | |
93 | + } | |
94 | + | |
95 | + @After | |
96 | + public void afterTest() throws Exception { | |
97 | + loginSysAdmin(); | |
98 | + | |
99 | + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) | |
100 | + .andExpect(status().isOk()); | |
101 | + } | |
102 | + | |
103 | + @Test | |
104 | + public void testFindEntityViewById() throws Exception { | |
105 | + EntityView savedView = getNewSavedEntityView("Test entity view"); | |
106 | + EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); | |
107 | + Assert.assertNotNull(foundView); | |
108 | + assertEquals(savedView, foundView); | |
109 | + } | |
110 | + | |
111 | + @Test | |
112 | + public void testSaveEntityView() throws Exception { | |
113 | + EntityView savedView = getNewSavedEntityView("Test entity view"); | |
114 | + | |
115 | + Assert.assertNotNull(savedView); | |
116 | + Assert.assertNotNull(savedView.getId()); | |
117 | + Assert.assertTrue(savedView.getCreatedTime() > 0); | |
118 | + assertEquals(savedTenant.getId(), savedView.getTenantId()); | |
119 | + Assert.assertNotNull(savedView.getCustomerId()); | |
120 | + assertEquals(NULL_UUID, savedView.getCustomerId().getId()); | |
121 | + assertEquals(savedView.getName(), savedView.getName()); | |
122 | + | |
123 | + savedView.setName("New test entity view"); | |
124 | + doPost("/api/entityView", savedView, EntityView.class); | |
125 | + EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); | |
126 | + | |
127 | + assertEquals(foundEntityView.getName(), savedView.getName()); | |
128 | + assertEquals(foundEntityView.getKeys(), telemetry); | |
129 | + } | |
130 | + | |
131 | + @Test | |
132 | + public void testDeleteEntityView() throws Exception { | |
133 | + EntityView view = getNewSavedEntityView("Test entity view"); | |
134 | + Customer customer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class); | |
135 | + view.setCustomerId(customer.getId()); | |
136 | + EntityView savedView = doPost("/api/entityView", view, EntityView.class); | |
137 | + | |
138 | + doDelete("/api/entityView/" + savedView.getId().getId().toString()) | |
139 | + .andExpect(status().isOk()); | |
140 | + | |
141 | + doGet("/api/entityView/" + savedView.getId().getId().toString()) | |
142 | + .andExpect(status().isNotFound()); | |
143 | + } | |
144 | + | |
145 | + @Test | |
146 | + public void testSaveEntityViewWithEmptyName() throws Exception { | |
147 | + doPost("/api/entityView", new EntityView()) | |
148 | + .andExpect(status().isBadRequest()) | |
149 | + .andExpect(statusReason(containsString("Entity view name should be specified!"))); | |
150 | + } | |
151 | + | |
152 | + @Test | |
153 | + public void testAssignAndUnAssignedEntityViewToCustomer() throws Exception { | |
154 | + EntityView view = getNewSavedEntityView("Test entity view"); | |
155 | + Customer savedCustomer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class); | |
156 | + view.setCustomerId(savedCustomer.getId()); | |
157 | + EntityView savedView = doPost("/api/entityView", view, EntityView.class); | |
158 | + | |
159 | + EntityView assignedView = doPost( | |
160 | + "/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString(), | |
161 | + EntityView.class); | |
162 | + assertEquals(savedCustomer.getId(), assignedView.getCustomerId()); | |
163 | + | |
164 | + EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); | |
165 | + assertEquals(savedCustomer.getId(), foundView.getCustomerId()); | |
166 | + | |
167 | + EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class); | |
168 | + assertEquals(ModelConstants.NULL_UUID, unAssignedView.getCustomerId().getId()); | |
169 | + | |
170 | + foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); | |
171 | + assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); | |
172 | + } | |
173 | + | |
174 | + @Test | |
175 | + public void testAssignEntityViewToNonExistentCustomer() throws Exception { | |
176 | + EntityView savedView = getNewSavedEntityView("Test entity view"); | |
177 | + doPost("/api/customer/" + UUIDs.timeBased().toString() + "/device/" + savedView.getId().getId().toString()) | |
178 | + .andExpect(status().isNotFound()); | |
179 | + } | |
180 | + | |
181 | + @Test | |
182 | + public void testAssignEntityViewToCustomerFromDifferentTenant() throws Exception { | |
183 | + loginSysAdmin(); | |
184 | + | |
185 | + Tenant tenant2 = getNewTenant("Different tenant"); | |
186 | + Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); | |
187 | + Assert.assertNotNull(savedTenant2); | |
188 | + | |
189 | + User tenantAdmin2 = new User(); | |
190 | + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); | |
191 | + tenantAdmin2.setTenantId(savedTenant2.getId()); | |
192 | + tenantAdmin2.setEmail("tenant3@thingsboard.org"); | |
193 | + tenantAdmin2.setFirstName("Joe"); | |
194 | + tenantAdmin2.setLastName("Downs"); | |
195 | + createUserAndLogin(tenantAdmin2, "testPassword1"); | |
196 | + | |
197 | + Customer customer = getNewCustomer("Different customer"); | |
198 | + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); | |
199 | + | |
200 | + login(tenantAdmin.getEmail(), "testPassword1"); | |
201 | + | |
202 | + EntityView savedView = getNewSavedEntityView("Test entity view"); | |
203 | + | |
204 | + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString()) | |
205 | + .andExpect(status().isForbidden()); | |
206 | + | |
207 | + loginSysAdmin(); | |
208 | + | |
209 | + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) | |
210 | + .andExpect(status().isOk()); | |
211 | + } | |
212 | + | |
213 | + @Test | |
214 | + public void testGetCustomerEntityViews() throws Exception { | |
215 | + CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId(); | |
216 | + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; | |
217 | + | |
218 | + List<EntityView> views = new ArrayList<>(); | |
219 | + for (int i = 0; i < 128; i++) { | |
220 | + views.add(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" | |
221 | + + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class)); | |
222 | + } | |
223 | + | |
224 | + List<EntityView> loadedViews = loadListOf(new TextPageLink(23), urlTemplate); | |
225 | + | |
226 | + Collections.sort(views, idComparator); | |
227 | + Collections.sort(loadedViews, idComparator); | |
228 | + | |
229 | + assertEquals(views, loadedViews); | |
230 | + } | |
231 | + | |
232 | + @Test | |
233 | + public void testGetCustomerEntityViewsByName() throws Exception { | |
234 | + CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId(); | |
235 | + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; | |
236 | + | |
237 | + String name1 = "Entity view name1"; | |
238 | + List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString() | |
239 | + + "/entityView/"); | |
240 | + List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate); | |
241 | + Collections.sort(namesOfView1, idComparator); | |
242 | + Collections.sort(loadedNamesOfView1, idComparator); | |
243 | + assertEquals(namesOfView1, loadedNamesOfView1); | |
244 | + | |
245 | + String name2 = "Entity view name2"; | |
246 | + List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString() | |
247 | + + "/entityView/"); | |
248 | + List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate); | |
249 | + Collections.sort(NamesOfView2, idComparator); | |
250 | + Collections.sort(loadedNamesOfView2, idComparator); | |
251 | + assertEquals(NamesOfView2, loadedNamesOfView2); | |
252 | + | |
253 | + for (EntityView view : loadedNamesOfView1) { | |
254 | + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); | |
255 | + } | |
256 | + TextPageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate, | |
257 | + new TypeReference<TextPageData<EntityView>>() { | |
258 | + }, new TextPageLink(4, name1)); | |
259 | + Assert.assertFalse(pageData.hasNext()); | |
260 | + assertEquals(0, pageData.getData().size()); | |
261 | + | |
262 | + for (EntityView view : loadedNamesOfView2) { | |
263 | + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); | |
264 | + } | |
265 | + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() { | |
266 | + }, | |
267 | + new TextPageLink(4, name2)); | |
268 | + Assert.assertFalse(pageData.hasNext()); | |
269 | + assertEquals(0, pageData.getData().size()); | |
270 | + } | |
271 | + | |
272 | + @Test | |
273 | + public void testGetTenantEntityViews() throws Exception { | |
274 | + | |
275 | + List<EntityView> views = new ArrayList<>(); | |
276 | + for (int i = 0; i < 178; i++) { | |
277 | + views.add(getNewSavedEntityView("Test entity view" + i)); | |
278 | + } | |
279 | + List<EntityView> loadedViews = loadListOf(new TextPageLink(23), "/api/tenant/entityViews?"); | |
280 | + | |
281 | + Collections.sort(views, idComparator); | |
282 | + Collections.sort(loadedViews, idComparator); | |
283 | + | |
284 | + assertEquals(views, loadedViews); | |
285 | + } | |
286 | + | |
287 | + @Test | |
288 | + public void testGetTenantEntityViewsByName() throws Exception { | |
289 | + String name1 = "Entity view name1"; | |
290 | + List<EntityView> namesOfView1 = fillListOf(143, name1); | |
291 | + List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), "/api/tenant/entityViews?"); | |
292 | + Collections.sort(namesOfView1, idComparator); | |
293 | + Collections.sort(loadedNamesOfView1, idComparator); | |
294 | + assertEquals(namesOfView1, loadedNamesOfView1); | |
295 | + | |
296 | + String name2 = "Entity view name2"; | |
297 | + List<EntityView> NamesOfView2 = fillListOf(75, name2); | |
298 | + List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), "/api/tenant/entityViews?"); | |
299 | + Collections.sort(NamesOfView2, idComparator); | |
300 | + Collections.sort(loadedNamesOfView2, idComparator); | |
301 | + assertEquals(NamesOfView2, loadedNamesOfView2); | |
302 | + | |
303 | + for (EntityView view : loadedNamesOfView1) { | |
304 | + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); | |
305 | + } | |
306 | + TextPageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", | |
307 | + new TypeReference<TextPageData<EntityView>>() { | |
308 | + }, new TextPageLink(4, name1)); | |
309 | + Assert.assertFalse(pageData.hasNext()); | |
310 | + assertEquals(0, pageData.getData().size()); | |
311 | + | |
312 | + for (EntityView view : loadedNamesOfView2) { | |
313 | + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); | |
314 | + } | |
315 | + pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference<TextPageData<EntityView>>() { | |
316 | + }, | |
317 | + new TextPageLink(4, name2)); | |
318 | + Assert.assertFalse(pageData.hasNext()); | |
319 | + assertEquals(0, pageData.getData().size()); | |
320 | + } | |
321 | + | |
322 | + @Test | |
323 | + public void testTheCopyOfAttrsIntoTSForTheView() throws Exception { | |
324 | + Set<String> actualAttributesSet = | |
325 | + getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}"); | |
326 | + | |
327 | + Set<String> expectedActualAttributesSet = | |
328 | + new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4")); | |
329 | + assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); | |
330 | + Thread.sleep(1000); | |
331 | + | |
332 | + EntityView savedView = getNewSavedEntityView("Test entity view"); | |
333 | + List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + | |
334 | + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); | |
335 | + | |
336 | + assertEquals("value1", getValue(values, "caValue1")); | |
337 | + assertEquals(true, getValue(values, "caValue2")); | |
338 | + assertEquals(42.0, getValue(values, "caValue3")); | |
339 | + assertEquals(73, getValue(values, "caValue4")); | |
340 | + } | |
341 | + | |
342 | + @Test | |
343 | + public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception { | |
344 | + Set<String> actualAttributesSet = | |
345 | + getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}"); | |
346 | + | |
347 | + Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4")); | |
348 | + assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); | |
349 | + Thread.sleep(1000); | |
350 | + | |
351 | + List<Map<String, Object>> valueTelemetryOfDevices = doGetAsync("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + | |
352 | + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); | |
353 | + | |
354 | + EntityView view = new EntityView(); | |
355 | + view.setEntityId(testDevice.getId()); | |
356 | + view.setTenantId(savedTenant.getId()); | |
357 | + view.setName("Test entity view"); | |
358 | + view.setKeys(telemetry); | |
359 | + view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10); | |
360 | + view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10); | |
361 | + EntityView savedView = doPost("/api/entityView", view, EntityView.class); | |
362 | + | |
363 | + List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + | |
364 | + "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); | |
365 | + assertEquals(0, values.size()); | |
366 | + } | |
367 | + | |
368 | + private Set<String> getAttributesByKeys(String stringKV) throws Exception { | |
369 | + String viewDeviceId = testDevice.getId().getId().toString(); | |
370 | + DeviceCredentials deviceCredentials = | |
371 | + doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); | |
372 | + assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); | |
373 | + | |
374 | + String accessToken = deviceCredentials.getCredentialsId(); | |
375 | + assertNotNull(accessToken); | |
376 | + | |
377 | + String clientId = MqttAsyncClient.generateClientId(); | |
378 | + MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId); | |
379 | + | |
380 | + MqttConnectOptions options = new MqttConnectOptions(); | |
381 | + options.setUserName(accessToken); | |
382 | + client.connect(options); | |
383 | + Thread.sleep(3000); | |
384 | + | |
385 | + MqttMessage message = new MqttMessage(); | |
386 | + message.setPayload((stringKV).getBytes()); | |
387 | + client.publish("v1/devices/me/attributes", message); | |
388 | + Thread.sleep(1000); | |
389 | + | |
390 | + return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class)); | |
391 | + } | |
392 | + | |
393 | + private Object getValue(List<Map<String, Object>> values, String stringValue) { | |
394 | + return values.size() == 0 ? null : | |
395 | + values.stream() | |
396 | + .filter(value -> value.get("key").equals(stringValue)) | |
397 | + .findFirst().get().get("value"); | |
398 | + } | |
399 | + | |
400 | + private EntityView getNewSavedEntityView(String name) throws Exception { | |
401 | + EntityView view = new EntityView(); | |
402 | + view.setEntityId(testDevice.getId()); | |
403 | + view.setTenantId(savedTenant.getId()); | |
404 | + view.setName(name); | |
405 | + view.setKeys(telemetry); | |
406 | + return doPost("/api/entityView", view, EntityView.class); | |
407 | + } | |
408 | + | |
409 | + private Customer getNewCustomer(String title) { | |
410 | + Customer customer = new Customer(); | |
411 | + customer.setTitle(title); | |
412 | + return customer; | |
413 | + } | |
414 | + | |
415 | + private Tenant getNewTenant(String title) { | |
416 | + Tenant tenant = new Tenant(); | |
417 | + tenant.setTitle(title); | |
418 | + return tenant; | |
419 | + } | |
420 | + | |
421 | + private List<EntityView> fillListOf(int limit, String partOfName, String urlTemplate) throws Exception { | |
422 | + List<EntityView> views = new ArrayList<>(); | |
423 | + for (EntityView view : fillListOf(limit, partOfName)) { | |
424 | + views.add(doPost(urlTemplate + view.getId().getId().toString(), EntityView.class)); | |
425 | + } | |
426 | + return views; | |
427 | + } | |
428 | + | |
429 | + private List<EntityView> fillListOf(int limit, String partOfName) throws Exception { | |
430 | + List<EntityView> viewNames = new ArrayList<>(); | |
431 | + for (int i = 0; i < limit; i++) { | |
432 | + String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15); | |
433 | + fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase(); | |
434 | + EntityView view = getNewSavedEntityView(fullName); | |
435 | + Customer customer = getNewCustomer("Test customer " + String.valueOf(Math.random())); | |
436 | + view.setCustomerId(doPost("/api/customer", customer, Customer.class).getId()); | |
437 | + viewNames.add(doPost("/api/entityView", view, EntityView.class)); | |
438 | + } | |
439 | + return viewNames; | |
440 | + } | |
441 | + | |
442 | + private List<EntityView> loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception { | |
443 | + List<EntityView> loadedItems = new ArrayList<>(); | |
444 | + TextPageData<EntityView> pageData; | |
445 | + do { | |
446 | + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() { | |
447 | + }, pageLink); | |
448 | + loadedItems.addAll(pageData.getData()); | |
449 | + if (pageData.hasNext()) { | |
450 | + pageLink = pageData.getNextPageLink(); | |
451 | + } | |
452 | + } while (pageData.hasNext()); | |
453 | + | |
454 | + return loadedItems; | |
455 | + } | |
456 | +} | ... | ... |
... | ... | @@ -32,7 +32,8 @@ public class ControllerNoSqlTestSuite { |
32 | 32 | public static CustomCassandraCQLUnit cassandraUnit = |
33 | 33 | new CustomCassandraCQLUnit( |
34 | 34 | Arrays.asList( |
35 | - new ClassPathCQLDataSet("cassandra/schema.cql", false, false), | |
35 | + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), | |
36 | + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), | |
36 | 37 | new ClassPathCQLDataSet("cassandra/system-data.cql", false, false), |
37 | 38 | new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)), |
38 | 39 | "cassandra-test.yaml", 30000l); | ... | ... |
... | ... | @@ -24,13 +24,13 @@ import java.util.Arrays; |
24 | 24 | |
25 | 25 | @RunWith(ClasspathSuite.class) |
26 | 26 | @ClasspathSuite.ClassnameFilters({ |
27 | - "org.thingsboard.server.controller.sql.*SqlTest", | |
27 | + "org.thingsboard.server.controller.sql.*Test", | |
28 | 28 | }) |
29 | 29 | public class ControllerSqlTestSuite { |
30 | 30 | |
31 | 31 | @ClassRule |
32 | 32 | public static CustomSqlUnit sqlUnit = new CustomSqlUnit( |
33 | - Arrays.asList("sql/schema.sql", "sql/system-data.sql"), | |
33 | + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), | |
34 | 34 | "sql/drop-all-tables.sql", |
35 | 35 | "sql-test.properties"); |
36 | 36 | } | ... | ... |
application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller.nosql; | |
17 | + | |
18 | +import org.thingsboard.server.controller.BaseEntityViewControllerTest; | |
19 | +import org.thingsboard.server.dao.service.DaoNoSqlTest; | |
20 | + | |
21 | +/** | |
22 | + * Created by Victor Basanets on 8/27/2017. | |
23 | + */ | |
24 | +@DaoNoSqlTest | |
25 | +public class EntityViewControllerNoSqlTest extends BaseEntityViewControllerTest { | |
26 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.controller.sql; | |
17 | + | |
18 | +import org.junit.Assert; | |
19 | +import org.junit.Test; | |
20 | +import org.thingsboard.server.common.data.EntityView; | |
21 | +import org.thingsboard.server.controller.BaseEntityViewControllerTest; | |
22 | +import org.thingsboard.server.dao.service.DaoSqlTest; | |
23 | + | |
24 | +import java.util.Arrays; | |
25 | + | |
26 | +/** | |
27 | + * Created by Victor Basanets on 8/27/2017. | |
28 | + */ | |
29 | +@DaoSqlTest | |
30 | +public class EntityViewControllerSqlTest extends BaseEntityViewControllerTest { | |
31 | +} | ... | ... |
... | ... | @@ -32,7 +32,8 @@ public class MqttNoSqlTestSuite { |
32 | 32 | public static CustomCassandraCQLUnit cassandraUnit = |
33 | 33 | new CustomCassandraCQLUnit( |
34 | 34 | Arrays.asList( |
35 | - new ClassPathCQLDataSet("cassandra/schema.cql", false, false), | |
35 | + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), | |
36 | + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), | |
36 | 37 | new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), |
37 | 38 | "cassandra-test.yaml", 30000l); |
38 | 39 | } | ... | ... |
... | ... | @@ -15,11 +15,9 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.mqtt; |
17 | 17 | |
18 | -import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; | |
19 | 18 | import org.junit.ClassRule; |
20 | 19 | import org.junit.extensions.cpsuite.ClasspathSuite; |
21 | 20 | import org.junit.runner.RunWith; |
22 | -import org.thingsboard.server.dao.CustomCassandraCQLUnit; | |
23 | 21 | import org.thingsboard.server.dao.CustomSqlUnit; |
24 | 22 | |
25 | 23 | import java.util.Arrays; |
... | ... | @@ -31,7 +29,7 @@ public class MqttSqlTestSuite { |
31 | 29 | |
32 | 30 | @ClassRule |
33 | 31 | public static CustomSqlUnit sqlUnit = new CustomSqlUnit( |
34 | - Arrays.asList("sql/schema.sql", "sql/system-data.sql"), | |
32 | + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), | |
35 | 33 | "sql/drop-all-tables.sql", |
36 | 34 | "sql-test.properties"); |
37 | 35 | } | ... | ... |
... | ... | @@ -35,7 +35,8 @@ public class RuleEngineNoSqlTestSuite { |
35 | 35 | public static CustomCassandraCQLUnit cassandraUnit = |
36 | 36 | new CustomCassandraCQLUnit( |
37 | 37 | Arrays.asList( |
38 | - new ClassPathCQLDataSet("cassandra/schema.cql", false, false), | |
38 | + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), | |
39 | + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), | |
39 | 40 | new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), |
40 | 41 | "cassandra-test.yaml", 30000l); |
41 | 42 | ... | ... |
... | ... | @@ -30,7 +30,7 @@ public class RuleEngineSqlTestSuite { |
30 | 30 | |
31 | 31 | @ClassRule |
32 | 32 | public static CustomSqlUnit sqlUnit = new CustomSqlUnit( |
33 | - Arrays.asList("sql/schema.sql", "sql/system-data.sql"), | |
33 | + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), | |
34 | 34 | "sql/drop-all-tables.sql", |
35 | 35 | "sql-test.properties"); |
36 | 36 | } | ... | ... |
... | ... | @@ -34,7 +34,8 @@ public class SystemNoSqlTestSuite { |
34 | 34 | public static CustomCassandraCQLUnit cassandraUnit = |
35 | 35 | new CustomCassandraCQLUnit( |
36 | 36 | Arrays.asList( |
37 | - new ClassPathCQLDataSet("cassandra/schema.cql", false, false), | |
37 | + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), | |
38 | + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), | |
38 | 39 | new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), |
39 | 40 | "cassandra-test.yaml", 30000l); |
40 | 41 | } | ... | ... |
... | ... | @@ -31,7 +31,7 @@ public class SystemSqlTestSuite { |
31 | 31 | |
32 | 32 | @ClassRule |
33 | 33 | public static CustomSqlUnit sqlUnit = new CustomSqlUnit( |
34 | - Arrays.asList("sql/schema.sql", "sql/system-data.sql"), | |
34 | + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), | |
35 | 35 | "sql/drop-all-tables.sql", |
36 | 36 | "sql-test.properties"); |
37 | 37 | ... | ... |
... | ... | @@ -20,4 +20,5 @@ public class CacheConstants { |
20 | 20 | public static final String RELATIONS_CACHE = "relations"; |
21 | 21 | public static final String DEVICE_CACHE = "devices"; |
22 | 22 | public static final String ASSET_CACHE = "assets"; |
23 | + public static final String ENTITY_VIEW_CACHE = "entityViews"; | |
23 | 24 | } | ... | ... |
... | ... | @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; |
19 | 19 | * @author Andrew Shvayka |
20 | 20 | */ |
21 | 21 | public enum EntityType { |
22 | - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE; | |
22 | + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW | |
23 | 23 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data; | |
17 | + | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
20 | +import lombok.EqualsAndHashCode; | |
21 | +import org.thingsboard.server.common.data.id.CustomerId; | |
22 | +import org.thingsboard.server.common.data.id.EntityId; | |
23 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
24 | +import org.thingsboard.server.common.data.id.TenantId; | |
25 | +import org.thingsboard.server.common.data.objects.TelemetryEntityView; | |
26 | + | |
27 | +/** | |
28 | + * Created by Victor Basanets on 8/27/2017. | |
29 | + */ | |
30 | + | |
31 | +@Data | |
32 | +@AllArgsConstructor | |
33 | +@EqualsAndHashCode(callSuper = true) | |
34 | +public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> | |
35 | + implements HasName, HasTenantId, HasCustomerId { | |
36 | + | |
37 | + private static final long serialVersionUID = 5582010124562018986L; | |
38 | + | |
39 | + private EntityId entityId; | |
40 | + private TenantId tenantId; | |
41 | + private CustomerId customerId; | |
42 | + private String name; | |
43 | + private TelemetryEntityView keys; | |
44 | + private long startTimeMs; | |
45 | + private long endTimeMs; | |
46 | + | |
47 | + public EntityView() { | |
48 | + super(); | |
49 | + } | |
50 | + | |
51 | + public EntityView(EntityViewId id) { | |
52 | + super(id); | |
53 | + } | |
54 | + | |
55 | + public EntityView(EntityView entityView) { | |
56 | + super(entityView); | |
57 | + } | |
58 | + | |
59 | + @Override | |
60 | + public String getSearchText() { | |
61 | + return getName() /*What the ...*/; | |
62 | + } | |
63 | + | |
64 | + @Override | |
65 | + public CustomerId getCustomerId() { | |
66 | + return customerId; | |
67 | + } | |
68 | + | |
69 | + @Override | |
70 | + public String getName() { | |
71 | + return name; | |
72 | + } | |
73 | + | |
74 | + @Override | |
75 | + public TenantId getTenantId() { | |
76 | + return tenantId; | |
77 | + } | |
78 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.entityview; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.server.common.data.EntityType; | |
20 | +import org.thingsboard.server.common.data.relation.EntityRelation; | |
21 | +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; | |
22 | +import org.thingsboard.server.common.data.relation.EntityTypeFilter; | |
23 | +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; | |
24 | + | |
25 | +import java.util.Collections; | |
26 | +import java.util.List; | |
27 | + | |
28 | +@Data | |
29 | +public class EntityViewSearchQuery { | |
30 | + | |
31 | + private RelationsSearchParameters parameters; | |
32 | + private String relationType; | |
33 | + | |
34 | + public EntityRelationsQuery toEntitySearchQuery() { | |
35 | + EntityRelationsQuery query = new EntityRelationsQuery(); | |
36 | + query.setParameters(parameters); | |
37 | + query.setFilters( | |
38 | + Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType, | |
39 | + Collections.singletonList(EntityType.ENTITY_VIEW)))); | |
40 | + return query; | |
41 | + } | |
42 | +} | ... | ... |
... | ... | @@ -57,6 +57,8 @@ public class EntityIdFactory { |
57 | 57 | return new RuleChainId(uuid); |
58 | 58 | case RULE_NODE: |
59 | 59 | return new RuleNodeId(uuid); |
60 | + case ENTITY_VIEW: | |
61 | + return new EntityViewId(uuid); | |
60 | 62 | } |
61 | 63 | throw new IllegalArgumentException("EntityType " + type + " is not supported!"); |
62 | 64 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.id; | |
17 | + | |
18 | +import com.fasterxml.jackson.annotation.JsonCreator; | |
19 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
20 | +import org.thingsboard.server.common.data.EntityType; | |
21 | + | |
22 | +import java.util.UUID; | |
23 | + | |
24 | +/** | |
25 | + * Created by Victor Basanets on 8/27/2017. | |
26 | + */ | |
27 | +public class EntityViewId extends UUIDBased implements EntityId { | |
28 | + | |
29 | + private static final long serialVersionUID = 1L; | |
30 | + | |
31 | + @JsonCreator | |
32 | + public EntityViewId(@JsonProperty("id") UUID id) { | |
33 | + super(id); | |
34 | + } | |
35 | + | |
36 | + public static EntityViewId fromString(String entityViewID) { | |
37 | + return new EntityViewId(UUID.fromString(entityViewID)); | |
38 | + } | |
39 | + | |
40 | + @Override | |
41 | + public EntityType getEntityType() { | |
42 | + return EntityType.ENTITY_VIEW; | |
43 | + } | |
44 | +} | ... | ... |
... | ... | @@ -42,4 +42,8 @@ public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery { |
42 | 42 | this(key, startTs, endTs, endTs - startTs, 1, Aggregation.AVG, "DESC"); |
43 | 43 | } |
44 | 44 | |
45 | + public BaseReadTsKvQuery(String key, long startTs, long endTs, int limit, String orderBy) { | |
46 | + this(key, startTs, endTs, endTs - startTs, limit, Aggregation.NONE, orderBy); | |
47 | + } | |
48 | + | |
45 | 49 | } | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.objects; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import lombok.NoArgsConstructor; | |
20 | + | |
21 | +import java.util.ArrayList; | |
22 | +import java.util.List; | |
23 | + | |
24 | +/** | |
25 | + * Created by Victor Basanets on 9/05/2017. | |
26 | + */ | |
27 | +@Data | |
28 | +@NoArgsConstructor | |
29 | +public class AttributesEntityView { | |
30 | + | |
31 | + private List<String> cs = new ArrayList<>(); | |
32 | + private List<String> ss = new ArrayList<>(); | |
33 | + private List<String> sh = new ArrayList<>(); | |
34 | + | |
35 | + public AttributesEntityView(List<String> cs, | |
36 | + List<String> ss, | |
37 | + List<String> sh) { | |
38 | + | |
39 | + this.cs = new ArrayList<>(cs); | |
40 | + this.ss = new ArrayList<>(ss); | |
41 | + this.sh = new ArrayList<>(sh); | |
42 | + } | |
43 | + | |
44 | + public AttributesEntityView(AttributesEntityView obj) { | |
45 | + this(obj.getCs(), obj.getSs(), obj.getSh()); | |
46 | + } | |
47 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.objects; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import lombok.NoArgsConstructor; | |
20 | + | |
21 | +import java.util.ArrayList; | |
22 | +import java.util.List; | |
23 | + | |
24 | +/** | |
25 | + * Created by Victor Basanets on 9/05/2017. | |
26 | + */ | |
27 | +@Data | |
28 | +@NoArgsConstructor | |
29 | +public class TelemetryEntityView { | |
30 | + | |
31 | + private List<String> timeseries; | |
32 | + private AttributesEntityView attributes; | |
33 | + | |
34 | + public TelemetryEntityView(List<String> timeseries, AttributesEntityView attributes) { | |
35 | + | |
36 | + this.timeseries = new ArrayList<>(timeseries); | |
37 | + this.attributes = attributes; | |
38 | + } | |
39 | + | |
40 | + public TelemetryEntityView(TelemetryEntityView obj) { | |
41 | + this(obj.getTimeseries(), obj.getAttributes()); | |
42 | + } | |
43 | +} | ... | ... |
... | ... | @@ -30,6 +30,7 @@ import org.springframework.util.StringUtils; |
30 | 30 | import org.thingsboard.server.common.data.Customer; |
31 | 31 | import org.thingsboard.server.common.data.EntitySubtype; |
32 | 32 | import org.thingsboard.server.common.data.EntityType; |
33 | +import org.thingsboard.server.common.data.EntityView; | |
33 | 34 | import org.thingsboard.server.common.data.Tenant; |
34 | 35 | import org.thingsboard.server.common.data.asset.Asset; |
35 | 36 | import org.thingsboard.server.common.data.asset.AssetSearchQuery; |
... | ... | @@ -43,6 +44,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; |
43 | 44 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
44 | 45 | import org.thingsboard.server.dao.customer.CustomerDao; |
45 | 46 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
47 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
46 | 48 | import org.thingsboard.server.dao.exception.DataValidationException; |
47 | 49 | import org.thingsboard.server.dao.service.DataValidator; |
48 | 50 | import org.thingsboard.server.dao.service.PaginatedRemover; |
... | ... | @@ -77,6 +79,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ |
77 | 79 | private CustomerDao customerDao; |
78 | 80 | |
79 | 81 | @Autowired |
82 | + private EntityViewService entityViewService; | |
83 | + | |
84 | + @Autowired | |
80 | 85 | private CacheManager cacheManager; |
81 | 86 | |
82 | 87 | @Override |
... | ... | @@ -130,11 +135,21 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ |
130 | 135 | validateId(assetId, INCORRECT_ASSET_ID + assetId); |
131 | 136 | deleteEntityRelations(assetId); |
132 | 137 | |
133 | - Cache cache = cacheManager.getCache(ASSET_CACHE); | |
134 | 138 | Asset asset = assetDao.findById(assetId.getId()); |
139 | + try { | |
140 | + List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get(); | |
141 | + if (entityViews != null && !entityViews.isEmpty()) { | |
142 | + throw new DataValidationException("Can't delete asset that is assigned to entity views!"); | |
143 | + } | |
144 | + } catch (Exception e) { | |
145 | + log.error("Exception while finding entity views for assetId [{}]", assetId, e); | |
146 | + throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e); | |
147 | + } | |
148 | + | |
135 | 149 | List<Object> list = new ArrayList<>(); |
136 | 150 | list.add(asset.getTenantId()); |
137 | 151 | list.add(asset.getName()); |
152 | + Cache cache = cacheManager.getCache(ASSET_CACHE); | |
138 | 153 | cache.evict(list); |
139 | 154 | |
140 | 155 | assetDao.removeById(assetId.getId()); | ... | ... |
... | ... | @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.cassandra; |
17 | 17 | |
18 | 18 | import org.springframework.beans.factory.annotation.Value; |
19 | 19 | import org.springframework.stereotype.Component; |
20 | -import org.thingsboard.server.dao.util.NoSqlDao; | |
20 | +import org.thingsboard.server.dao.util.NoSqlAnyDao; | |
21 | 21 | |
22 | 22 | import javax.annotation.PostConstruct; |
23 | 23 | |
24 | 24 | @Component |
25 | -@NoSqlDao | |
25 | +@NoSqlAnyDao | |
26 | 26 | public class CassandraCluster extends AbstractCassandraCluster { |
27 | 27 | |
28 | 28 | @Value("${cassandra.keyspace_name}") | ... | ... |
... | ... | @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.cassandra; |
17 | 17 | |
18 | 18 | import org.springframework.context.annotation.Profile; |
19 | 19 | import org.springframework.stereotype.Component; |
20 | -import org.thingsboard.server.dao.util.NoSqlDao; | |
20 | +import org.thingsboard.server.dao.util.NoSqlAnyDao; | |
21 | 21 | |
22 | 22 | import javax.annotation.PostConstruct; |
23 | 23 | |
24 | 24 | @Component |
25 | -@NoSqlDao | |
25 | +@NoSqlAnyDao | |
26 | 26 | @Profile("install") |
27 | 27 | public class CassandraInstallCluster extends AbstractCassandraCluster { |
28 | 28 | ... | ... |
... | ... | @@ -21,14 +21,14 @@ import lombok.Data; |
21 | 21 | import org.springframework.beans.factory.annotation.Value; |
22 | 22 | import org.springframework.context.annotation.Configuration; |
23 | 23 | import org.springframework.stereotype.Component; |
24 | -import org.thingsboard.server.dao.util.NoSqlDao; | |
24 | +import org.thingsboard.server.dao.util.NoSqlAnyDao; | |
25 | 25 | |
26 | 26 | import javax.annotation.PostConstruct; |
27 | 27 | |
28 | 28 | @Component |
29 | 29 | @Configuration |
30 | 30 | @Data |
31 | -@NoSqlDao | |
31 | +@NoSqlAnyDao | |
32 | 32 | public class CassandraQueryOptions { |
33 | 33 | |
34 | 34 | @Value("${cassandra.query.default_fetch_size}") | ... | ... |
... | ... | @@ -20,14 +20,14 @@ import lombok.Data; |
20 | 20 | import org.springframework.beans.factory.annotation.Value; |
21 | 21 | import org.springframework.context.annotation.Configuration; |
22 | 22 | import org.springframework.stereotype.Component; |
23 | -import org.thingsboard.server.dao.util.NoSqlDao; | |
23 | +import org.thingsboard.server.dao.util.NoSqlAnyDao; | |
24 | 24 | |
25 | 25 | import javax.annotation.PostConstruct; |
26 | 26 | |
27 | 27 | @Component |
28 | 28 | @Configuration |
29 | 29 | @Data |
30 | -@NoSqlDao | |
30 | +@NoSqlAnyDao | |
31 | 31 | public class CassandraSocketOptions { |
32 | 32 | |
33 | 33 | @Value("${cassandra.socket.connect_timeout}") | ... | ... |
... | ... | @@ -32,6 +32,7 @@ import org.thingsboard.server.dao.asset.AssetService; |
32 | 32 | import org.thingsboard.server.dao.dashboard.DashboardService; |
33 | 33 | import org.thingsboard.server.dao.device.DeviceService; |
34 | 34 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
35 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
35 | 36 | import org.thingsboard.server.dao.exception.DataValidationException; |
36 | 37 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
37 | 38 | import org.thingsboard.server.dao.service.DataValidator; |
... | ... | @@ -70,6 +71,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom |
70 | 71 | private DeviceService deviceService; |
71 | 72 | |
72 | 73 | @Autowired |
74 | + private EntityViewService entityViewService; | |
75 | + | |
76 | + @Autowired | |
73 | 77 | private DashboardService dashboardService; |
74 | 78 | |
75 | 79 | @Override |
... | ... | @@ -111,6 +115,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom |
111 | 115 | throw new IncorrectParameterException("Unable to delete non-existent customer."); |
112 | 116 | } |
113 | 117 | dashboardService.unassignCustomerDashboards(customerId); |
118 | + entityViewService.unassignCustomerEntityViews(customer.getTenantId(), customerId); | |
114 | 119 | assetService.unassignCustomerAssets(customer.getTenantId(), customerId); |
115 | 120 | deviceService.unassignCustomerDevices(customer.getTenantId(), customerId); |
116 | 121 | userService.deleteCustomerUsers(customer.getTenantId(), customerId); | ... | ... |
... | ... | @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.Customer; |
31 | 31 | import org.thingsboard.server.common.data.Device; |
32 | 32 | import org.thingsboard.server.common.data.EntitySubtype; |
33 | 33 | import org.thingsboard.server.common.data.EntityType; |
34 | +import org.thingsboard.server.common.data.EntityView; | |
34 | 35 | import org.thingsboard.server.common.data.Tenant; |
35 | 36 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
36 | 37 | import org.thingsboard.server.common.data.id.CustomerId; |
... | ... | @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; |
45 | 46 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
46 | 47 | import org.thingsboard.server.dao.customer.CustomerDao; |
47 | 48 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
49 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
48 | 50 | import org.thingsboard.server.dao.exception.DataValidationException; |
49 | 51 | import org.thingsboard.server.dao.service.DataValidator; |
50 | 52 | import org.thingsboard.server.dao.service.PaginatedRemover; |
... | ... | @@ -87,6 +89,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
87 | 89 | private DeviceCredentialsService deviceCredentialsService; |
88 | 90 | |
89 | 91 | @Autowired |
92 | + private EntityViewService entityViewService; | |
93 | + | |
94 | + @Autowired | |
90 | 95 | private CacheManager cacheManager; |
91 | 96 | |
92 | 97 | @Override |
... | ... | @@ -145,18 +150,31 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
145 | 150 | @Override |
146 | 151 | public void deleteDevice(DeviceId deviceId) { |
147 | 152 | log.trace("Executing deleteDevice [{}]", deviceId); |
148 | - Cache cache = cacheManager.getCache(DEVICE_CACHE); | |
149 | 153 | validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); |
154 | + | |
155 | + Device device = deviceDao.findById(deviceId.getId()); | |
156 | + try { | |
157 | + List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get(); | |
158 | + if (entityViews != null && !entityViews.isEmpty()) { | |
159 | + throw new DataValidationException("Can't delete device that is assigned to entity views!"); | |
160 | + } | |
161 | + } catch (Exception e) { | |
162 | + log.error("Exception while finding entity views for deviceId [{}]", deviceId, e); | |
163 | + throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e); | |
164 | + } | |
165 | + | |
150 | 166 | DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId); |
151 | 167 | if (deviceCredentials != null) { |
152 | 168 | deviceCredentialsService.deleteDeviceCredentials(deviceCredentials); |
153 | 169 | } |
154 | 170 | deleteEntityRelations(deviceId); |
155 | - Device device = deviceDao.findById(deviceId.getId()); | |
171 | + | |
156 | 172 | List<Object> list = new ArrayList<>(); |
157 | 173 | list.add(device.getTenantId()); |
158 | 174 | list.add(device.getName()); |
175 | + Cache cache = cacheManager.getCache(DEVICE_CACHE); | |
159 | 176 | cache.evict(list); |
177 | + | |
160 | 178 | deviceDao.removeById(deviceId.getId()); |
161 | 179 | } |
162 | 180 | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.entityview; | |
17 | + | |
18 | +import com.datastax.driver.core.Statement; | |
19 | +import com.datastax.driver.core.querybuilder.Select; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
22 | +import org.springframework.stereotype.Component; | |
23 | +import org.thingsboard.server.common.data.EntitySubtype; | |
24 | +import org.thingsboard.server.common.data.EntityType; | |
25 | +import org.thingsboard.server.common.data.EntityView; | |
26 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
27 | +import org.thingsboard.server.dao.DaoUtil; | |
28 | +import org.thingsboard.server.dao.model.EntitySubtypeEntity; | |
29 | +import org.thingsboard.server.dao.model.nosql.EntityViewEntity; | |
30 | +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; | |
31 | +import org.thingsboard.server.dao.util.NoSqlDao; | |
32 | + | |
33 | +import java.util.Arrays; | |
34 | +import java.util.Collections; | |
35 | +import java.util.List; | |
36 | +import java.util.Optional; | |
37 | +import java.util.UUID; | |
38 | + | |
39 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | |
40 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; | |
41 | +import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY; | |
42 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; | |
43 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF; | |
44 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF; | |
45 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_NAME; | |
46 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; | |
47 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_NAME_PROPERTY; | |
48 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME; | |
49 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY; | |
50 | +import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY; | |
51 | + | |
52 | +/** | |
53 | + * Created by Victor Basanets on 9/06/2017. | |
54 | + */ | |
55 | +@Component | |
56 | +@Slf4j | |
57 | +@NoSqlDao | |
58 | +public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<EntityViewEntity, EntityView> implements EntityViewDao { | |
59 | + | |
60 | + @Override | |
61 | + protected Class<EntityViewEntity> getColumnFamilyClass() { | |
62 | + return EntityViewEntity.class; | |
63 | + } | |
64 | + | |
65 | + @Override | |
66 | + protected String getColumnFamilyName() { | |
67 | + return ENTITY_VIEW_TABLE_FAMILY_NAME; | |
68 | + } | |
69 | + | |
70 | + @Override | |
71 | + public EntityView save(EntityView domain) { | |
72 | + EntityView savedEntityView = super.save(domain); | |
73 | + EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW, | |
74 | + savedEntityView.getId().getEntityType().toString()); | |
75 | + EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype); | |
76 | + Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity); | |
77 | + executeWrite(saveStatement); | |
78 | + return savedEntityView; | |
79 | + } | |
80 | + | |
81 | + @Override | |
82 | + public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) { | |
83 | + log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink); | |
84 | + List<EntityViewEntity> entityViewEntities = | |
85 | + findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, | |
86 | + Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink); | |
87 | + log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]", | |
88 | + entityViewEntities, tenantId, pageLink); | |
89 | + return DaoUtil.convertDataList(entityViewEntities); | |
90 | + } | |
91 | + | |
92 | + @Override | |
93 | + public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) { | |
94 | + Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where(); | |
95 | + query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)); | |
96 | + query.and(eq(ENTITY_VIEW_NAME_PROPERTY, name)); | |
97 | + return Optional.ofNullable(DaoUtil.getData(findOneByStatement(query))); | |
98 | + } | |
99 | + | |
100 | + @Override | |
101 | + public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { | |
102 | + log.debug("Try to find entity views by tenantId [{}], customerId[{}] and pageLink [{}]", | |
103 | + tenantId, customerId, pageLink); | |
104 | + List<EntityViewEntity> entityViewEntities = findPageWithTextSearch( | |
105 | + ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF, | |
106 | + Arrays.asList(eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)), | |
107 | + pageLink); | |
108 | + log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", | |
109 | + entityViewEntities, tenantId, customerId, pageLink); | |
110 | + return DaoUtil.convertDataList(entityViewEntities); | |
111 | + } | |
112 | + | |
113 | + @Override | |
114 | + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { | |
115 | + log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId); | |
116 | + Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF).where(); | |
117 | + query.and(eq(TENANT_ID_PROPERTY, tenantId)); | |
118 | + query.and(eq(ENTITY_ID_COLUMN, entityId)); | |
119 | + return findListByStatementAsync(query); | |
120 | + } | |
121 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.entityview; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import org.thingsboard.server.common.data.Device; | |
20 | +import org.thingsboard.server.common.data.EntityView; | |
21 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
22 | +import org.thingsboard.server.dao.Dao; | |
23 | + | |
24 | +import java.util.List; | |
25 | +import java.util.Optional; | |
26 | +import java.util.UUID; | |
27 | + | |
28 | +/** | |
29 | + * Created by Victor Basanets on 8/28/2017. | |
30 | + */ | |
31 | +public interface EntityViewDao extends Dao<EntityView> { | |
32 | + | |
33 | + /** | |
34 | + * Save or update device object | |
35 | + * | |
36 | + * @param entityView the entity-view object | |
37 | + * @return saved entity-view object | |
38 | + */ | |
39 | + EntityView save(EntityView entityView); | |
40 | + | |
41 | + /** | |
42 | + * Find entity views by tenantId and page link. | |
43 | + * | |
44 | + * @param tenantId the tenantId | |
45 | + * @param pageLink the page link | |
46 | + * @return the list of entity view objects | |
47 | + */ | |
48 | + List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink); | |
49 | + | |
50 | + /** | |
51 | + * Find entity views by tenantId and entity view name. | |
52 | + * | |
53 | + * @param tenantId the tenantId | |
54 | + * @param name the entity view name | |
55 | + * @return the optional entity view object | |
56 | + */ | |
57 | + Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name); | |
58 | + | |
59 | + /** | |
60 | + * Find entity views by tenantId, customerId and page link. | |
61 | + * | |
62 | + * @param tenantId the tenantId | |
63 | + * @param customerId the customerId | |
64 | + * @param pageLink the page link | |
65 | + * @return the list of entity view objects | |
66 | + */ | |
67 | + List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId, | |
68 | + UUID customerId, | |
69 | + TextPageLink pageLink); | |
70 | + | |
71 | + | |
72 | + ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId); | |
73 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.entityview; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import org.thingsboard.server.common.data.EntityView; | |
20 | +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; | |
21 | +import org.thingsboard.server.common.data.id.CustomerId; | |
22 | +import org.thingsboard.server.common.data.id.EntityId; | |
23 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
24 | +import org.thingsboard.server.common.data.id.TenantId; | |
25 | +import org.thingsboard.server.common.data.page.TextPageData; | |
26 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
27 | + | |
28 | +import java.util.List; | |
29 | + | |
30 | +/** | |
31 | + * Created by Victor Basanets on 8/27/2017. | |
32 | + */ | |
33 | +public interface EntityViewService { | |
34 | + | |
35 | + EntityView saveEntityView(EntityView entityView); | |
36 | + | |
37 | + EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId); | |
38 | + | |
39 | + EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId); | |
40 | + | |
41 | + void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId); | |
42 | + | |
43 | + EntityView findEntityViewById(EntityViewId entityViewId); | |
44 | + | |
45 | + TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); | |
46 | + | |
47 | + TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); | |
48 | + | |
49 | + ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query); | |
50 | + | |
51 | + ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId); | |
52 | + | |
53 | + ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId); | |
54 | + | |
55 | + void deleteEntityView(EntityViewId entityViewId); | |
56 | + | |
57 | + void deleteEntityViewsByTenantId(TenantId tenantId); | |
58 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.entityview; | |
17 | + | |
18 | +import com.google.common.util.concurrent.FutureCallback; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
22 | +import org.apache.commons.lang3.StringUtils; | |
23 | +import org.springframework.beans.factory.annotation.Autowired; | |
24 | +import org.springframework.cache.Cache; | |
25 | +import org.springframework.cache.CacheManager; | |
26 | +import org.springframework.cache.annotation.CacheEvict; | |
27 | +import org.springframework.cache.annotation.Cacheable; | |
28 | +import org.springframework.cache.annotation.Caching; | |
29 | +import org.springframework.stereotype.Service; | |
30 | +import org.thingsboard.server.common.data.Customer; | |
31 | +import org.thingsboard.server.common.data.DataConstants; | |
32 | +import org.thingsboard.server.common.data.EntityType; | |
33 | +import org.thingsboard.server.common.data.EntityView; | |
34 | +import org.thingsboard.server.common.data.Tenant; | |
35 | +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; | |
36 | +import org.thingsboard.server.common.data.id.CustomerId; | |
37 | +import org.thingsboard.server.common.data.id.EntityId; | |
38 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
39 | +import org.thingsboard.server.common.data.id.TenantId; | |
40 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
41 | +import org.thingsboard.server.common.data.page.TextPageData; | |
42 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
43 | +import org.thingsboard.server.common.data.relation.EntityRelation; | |
44 | +import org.thingsboard.server.common.data.relation.EntitySearchDirection; | |
45 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
46 | +import org.thingsboard.server.dao.customer.CustomerDao; | |
47 | +import org.thingsboard.server.dao.entity.AbstractEntityService; | |
48 | +import org.thingsboard.server.dao.exception.DataValidationException; | |
49 | +import org.thingsboard.server.dao.service.DataValidator; | |
50 | +import org.thingsboard.server.dao.service.PaginatedRemover; | |
51 | +import org.thingsboard.server.dao.tenant.TenantDao; | |
52 | + | |
53 | +import javax.annotation.Nullable; | |
54 | +import java.util.ArrayList; | |
55 | +import java.util.Arrays; | |
56 | +import java.util.Collection; | |
57 | +import java.util.List; | |
58 | +import java.util.concurrent.ExecutionException; | |
59 | +import java.util.stream.Collectors; | |
60 | + | |
61 | +import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; | |
62 | +import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE; | |
63 | +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; | |
64 | +import static org.thingsboard.server.dao.service.Validator.validateId; | |
65 | +import static org.thingsboard.server.dao.service.Validator.validatePageLink; | |
66 | + | |
67 | +/** | |
68 | + * Created by Victor Basanets on 8/28/2017. | |
69 | + */ | |
70 | +@Service | |
71 | +@Slf4j | |
72 | +public class EntityViewServiceImpl extends AbstractEntityService implements EntityViewService { | |
73 | + | |
74 | + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | |
75 | + public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; | |
76 | + public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; | |
77 | + public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId "; | |
78 | + | |
79 | + @Autowired | |
80 | + private EntityViewDao entityViewDao; | |
81 | + | |
82 | + @Autowired | |
83 | + private TenantDao tenantDao; | |
84 | + | |
85 | + @Autowired | |
86 | + private CustomerDao customerDao; | |
87 | + | |
88 | + @Autowired | |
89 | + private AttributesService attributesService; | |
90 | + | |
91 | + @Autowired | |
92 | + private CacheManager cacheManager; | |
93 | + | |
94 | + @Caching(evict = { | |
95 | + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"), | |
96 | + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")}) | |
97 | + @Override | |
98 | + public EntityView saveEntityView(EntityView entityView) { | |
99 | + log.trace("Executing save entity view [{}]", entityView); | |
100 | + entityViewValidator.validate(entityView); | |
101 | + EntityView savedEntityView = entityViewDao.save(entityView); | |
102 | + | |
103 | + List<ListenableFuture<List<Void>>> futures = new ArrayList<>(); | |
104 | + if (savedEntityView.getKeys() != null) { | |
105 | + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs())); | |
106 | + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs())); | |
107 | + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh())); | |
108 | + } | |
109 | + for (ListenableFuture<List<Void>> future : futures) { | |
110 | + try { | |
111 | + future.get(); | |
112 | + } catch (InterruptedException | ExecutionException e) { | |
113 | + log.error("Failed to copy attributes to entity view", e); | |
114 | + throw new RuntimeException("Failed to copy attributes to entity view", e); | |
115 | + } | |
116 | + } | |
117 | + return savedEntityView; | |
118 | + } | |
119 | + | |
120 | + @Override | |
121 | + public EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId) { | |
122 | + EntityView entityView = findEntityViewById(entityViewId); | |
123 | + entityView.setCustomerId(customerId); | |
124 | + return saveEntityView(entityView); | |
125 | + } | |
126 | + | |
127 | + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") | |
128 | + @Override | |
129 | + public EntityView unassignEntityViewFromCustomer(EntityViewId entityViewId) { | |
130 | + EntityView entityView = findEntityViewById(entityViewId); | |
131 | + entityView.setCustomerId(null); | |
132 | + return saveEntityView(entityView); | |
133 | + } | |
134 | + | |
135 | + @Override | |
136 | + public void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId) { | |
137 | + log.trace("Executing unassignCustomerEntityViews, tenantId [{}], customerId [{}]", tenantId, customerId); | |
138 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
139 | + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); | |
140 | + new CustomerEntityViewsUnAssigner(tenantId).removeEntities(customerId); | |
141 | + } | |
142 | + | |
143 | + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") | |
144 | + @Override | |
145 | + public EntityView findEntityViewById(EntityViewId entityViewId) { | |
146 | + log.trace("Executing findEntityViewById [{}]", entityViewId); | |
147 | + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); | |
148 | + return entityViewDao.findById(entityViewId.getId()); | |
149 | + } | |
150 | + | |
151 | + @Override | |
152 | + public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) { | |
153 | + log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); | |
154 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
155 | + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); | |
156 | + List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink); | |
157 | + return new TextPageData<>(entityViews, pageLink); | |
158 | + } | |
159 | + | |
160 | + @Override | |
161 | + public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, | |
162 | + TextPageLink pageLink) { | |
163 | + log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," + | |
164 | + " pageLink [{}]", tenantId, customerId, pageLink); | |
165 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
166 | + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); | |
167 | + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); | |
168 | + List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), | |
169 | + customerId.getId(), pageLink); | |
170 | + return new TextPageData<>(entityViews, pageLink); | |
171 | + } | |
172 | + | |
173 | + @Override | |
174 | + public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) { | |
175 | + ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery()); | |
176 | + ListenableFuture<List<EntityView>> entityViews = Futures.transformAsync(relations, r -> { | |
177 | + EntitySearchDirection direction = query.toEntitySearchQuery().getParameters().getDirection(); | |
178 | + List<ListenableFuture<EntityView>> futures = new ArrayList<>(); | |
179 | + for (EntityRelation relation : r) { | |
180 | + EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom(); | |
181 | + if (entityId.getEntityType() == EntityType.ENTITY_VIEW) { | |
182 | + futures.add(findEntityViewByIdAsync(new EntityViewId(entityId.getId()))); | |
183 | + } | |
184 | + } | |
185 | + return Futures.successfulAsList(futures); | |
186 | + }); | |
187 | + return entityViews; | |
188 | + } | |
189 | + | |
190 | + @Override | |
191 | + public ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId) { | |
192 | + log.trace("Executing findEntityViewById [{}]", entityViewId); | |
193 | + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); | |
194 | + return entityViewDao.findByIdAsync(entityViewId.getId()); | |
195 | + } | |
196 | + | |
197 | + @Override | |
198 | + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) { | |
199 | + log.trace("Executing findEntityViewsByTenantIdAndEntityIdAsync, tenantId [{}], entityId [{}]", tenantId, entityId); | |
200 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
201 | + validateId(entityId.getId(), "Incorrect entityId" + entityId); | |
202 | + | |
203 | + List<Object> tenantIdAndEntityId = new ArrayList<>(); | |
204 | + tenantIdAndEntityId.add(tenantId); | |
205 | + tenantIdAndEntityId.add(entityId); | |
206 | + | |
207 | + Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); | |
208 | + List<EntityView> fromCache = cache.get(tenantIdAndEntityId, List.class); | |
209 | + if (fromCache != null) { | |
210 | + return Futures.immediateFuture(fromCache); | |
211 | + } else { | |
212 | + ListenableFuture<List<EntityView>> entityViewsFuture = entityViewDao.findEntityViewsByTenantIdAndEntityIdAsync(tenantId.getId(), entityId.getId()); | |
213 | + Futures.addCallback(entityViewsFuture, | |
214 | + new FutureCallback<List<EntityView>>() { | |
215 | + @Override | |
216 | + public void onSuccess(@Nullable List<EntityView> result) { | |
217 | + cache.putIfAbsent(tenantIdAndEntityId, result); | |
218 | + } | |
219 | + @Override | |
220 | + public void onFailure(Throwable t) { | |
221 | + log.error("Error while finding entity views by tenantId and entityId", t); | |
222 | + } | |
223 | + }); | |
224 | + return entityViewsFuture; | |
225 | + } | |
226 | + } | |
227 | + | |
228 | + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") | |
229 | + @Override | |
230 | + public void deleteEntityView(EntityViewId entityViewId) { | |
231 | + log.trace("Executing deleteEntityView [{}]", entityViewId); | |
232 | + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); | |
233 | + deleteEntityRelations(entityViewId); | |
234 | + EntityView entityView = entityViewDao.findById(entityViewId.getId()); | |
235 | + cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); | |
236 | + entityViewDao.removeById(entityViewId.getId()); | |
237 | + } | |
238 | + | |
239 | + @Override | |
240 | + public void deleteEntityViewsByTenantId(TenantId tenantId) { | |
241 | + log.trace("Executing deleteEntityViewsByTenantId, tenantId [{}]", tenantId); | |
242 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
243 | + tenantEntityViewRemover.removeEntities(tenantId); | |
244 | + } | |
245 | + | |
246 | + private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) { | |
247 | + if (keys != null && !keys.isEmpty()) { | |
248 | + ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); | |
249 | + return Futures.transform(getAttrFuture, attributeKvEntries -> { | |
250 | + List<AttributeKvEntry> filteredAttributes = new ArrayList<>(); | |
251 | + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { | |
252 | + filteredAttributes = | |
253 | + attributeKvEntries.stream() | |
254 | + .filter(attributeKvEntry -> { | |
255 | + long startTime = entityView.getStartTimeMs(); | |
256 | + long endTime = entityView.getEndTimeMs(); | |
257 | + long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); | |
258 | + return startTime == 0 && endTime == 0 || | |
259 | + (endTime == 0 && startTime < lastUpdateTs) || | |
260 | + (startTime == 0 && endTime > lastUpdateTs) | |
261 | + ? true : startTime < lastUpdateTs && endTime > lastUpdateTs; | |
262 | + }).collect(Collectors.toList()); | |
263 | + } | |
264 | + try { | |
265 | + return attributesService.save(entityView.getId(), scope, filteredAttributes).get(); | |
266 | + } catch (InterruptedException | ExecutionException e) { | |
267 | + log.error("Failed to copy attributes to entity view", e); | |
268 | + throw new RuntimeException("Failed to copy attributes to entity view", e); | |
269 | + } | |
270 | + }); | |
271 | + } else { | |
272 | + return Futures.immediateFuture(null); | |
273 | + } | |
274 | + } | |
275 | + | |
276 | + private DataValidator<EntityView> entityViewValidator = | |
277 | + new DataValidator<EntityView>() { | |
278 | + | |
279 | + @Override | |
280 | + protected void validateCreate(EntityView entityView) { | |
281 | + entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) | |
282 | + .ifPresent(e -> { | |
283 | + throw new DataValidationException("Entity view with such name already exists!"); | |
284 | + }); | |
285 | + } | |
286 | + | |
287 | + @Override | |
288 | + protected void validateUpdate(EntityView entityView) { | |
289 | + entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) | |
290 | + .ifPresent(e -> { | |
291 | + if (!e.getUuidId().equals(entityView.getUuidId())) { | |
292 | + throw new DataValidationException("Entity view with such name already exists!"); | |
293 | + } | |
294 | + }); | |
295 | + } | |
296 | + | |
297 | + @Override | |
298 | + protected void validateDataImpl(EntityView entityView) { | |
299 | + if (StringUtils.isEmpty(entityView.getName())) { | |
300 | + throw new DataValidationException("Entity view name should be specified!"); | |
301 | + } | |
302 | + if (entityView.getTenantId() == null) { | |
303 | + throw new DataValidationException("Entity view should be assigned to tenant!"); | |
304 | + } else { | |
305 | + Tenant tenant = tenantDao.findById(entityView.getTenantId().getId()); | |
306 | + if (tenant == null) { | |
307 | + throw new DataValidationException("Entity view is referencing to non-existent tenant!"); | |
308 | + } | |
309 | + } | |
310 | + if (entityView.getCustomerId() == null) { | |
311 | + entityView.setCustomerId(new CustomerId(NULL_UUID)); | |
312 | + } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) { | |
313 | + Customer customer = customerDao.findById(entityView.getCustomerId().getId()); | |
314 | + if (customer == null) { | |
315 | + throw new DataValidationException("Can't assign entity view to non-existent customer!"); | |
316 | + } | |
317 | + if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) { | |
318 | + throw new DataValidationException("Can't assign entity view to customer from different tenant!"); | |
319 | + } | |
320 | + } | |
321 | + } | |
322 | + }; | |
323 | + | |
324 | + private PaginatedRemover<TenantId, EntityView> tenantEntityViewRemover = | |
325 | + new PaginatedRemover<TenantId, EntityView>() { | |
326 | + | |
327 | + @Override | |
328 | + protected List<EntityView> findEntities(TenantId id, TextPageLink pageLink) { | |
329 | + return entityViewDao.findEntityViewsByTenantId(id.getId(), pageLink); | |
330 | + } | |
331 | + | |
332 | + @Override | |
333 | + protected void removeEntity(EntityView entity) { | |
334 | + deleteEntityView(new EntityViewId(entity.getUuidId())); | |
335 | + } | |
336 | + }; | |
337 | + | |
338 | + private class CustomerEntityViewsUnAssigner extends PaginatedRemover<CustomerId, EntityView> { | |
339 | + | |
340 | + private TenantId tenantId; | |
341 | + | |
342 | + CustomerEntityViewsUnAssigner(TenantId tenantId) { | |
343 | + this.tenantId = tenantId; | |
344 | + } | |
345 | + | |
346 | + @Override | |
347 | + protected List<EntityView> findEntities(CustomerId id, TextPageLink pageLink) { | |
348 | + return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); | |
349 | + } | |
350 | + | |
351 | + @Override | |
352 | + protected void removeEntity(EntityView entity) { | |
353 | + unassignEntityViewFromCustomer(new EntityViewId(entity.getUuidId())); | |
354 | + } | |
355 | + } | |
356 | +} | ... | ... |
... | ... | @@ -52,7 +52,6 @@ public class ModelConstants { |
52 | 52 | public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; |
53 | 53 | public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; |
54 | 54 | |
55 | - | |
56 | 55 | /** |
57 | 56 | * Cassandra user constants. |
58 | 57 | */ |
... | ... | @@ -130,12 +129,12 @@ public class ModelConstants { |
130 | 129 | * Cassandra device constants. |
131 | 130 | */ |
132 | 131 | public static final String DEVICE_COLUMN_FAMILY_NAME = "device"; |
132 | + public static final String DEVICE_FAMILY_NAME = "device"; | |
133 | 133 | public static final String DEVICE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; |
134 | 134 | public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; |
135 | 135 | public static final String DEVICE_NAME_PROPERTY = "name"; |
136 | 136 | public static final String DEVICE_TYPE_PROPERTY = "type"; |
137 | 137 | public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; |
138 | - | |
139 | 138 | public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; |
140 | 139 | public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; |
141 | 140 | public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text"; |
... | ... | @@ -144,6 +143,23 @@ public class ModelConstants { |
144 | 143 | public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; |
145 | 144 | |
146 | 145 | /** |
146 | + * Cassandra entityView constants. | |
147 | + */ | |
148 | + public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views"; | |
149 | + public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN; | |
150 | + public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; | |
151 | + public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; | |
152 | + public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY; | |
153 | + public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer"; | |
154 | + public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id"; | |
155 | + public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys"; | |
156 | + public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts"; | |
157 | + public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts"; | |
158 | + public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; | |
159 | + public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text"; | |
160 | + public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name"; | |
161 | + | |
162 | + /** | |
147 | 163 | * Cassandra audit log constants. |
148 | 164 | */ |
149 | 165 | public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.model.nosql; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.datastax.driver.mapping.annotations.Column; | |
20 | +import com.datastax.driver.mapping.annotations.PartitionKey; | |
21 | +import com.datastax.driver.mapping.annotations.Table; | |
22 | +import com.fasterxml.jackson.databind.JsonNode; | |
23 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
24 | +import lombok.Data; | |
25 | +import lombok.EqualsAndHashCode; | |
26 | +import lombok.ToString; | |
27 | +import lombok.extern.slf4j.Slf4j; | |
28 | +import org.hibernate.annotations.Type; | |
29 | +import org.thingsboard.server.common.data.EntityType; | |
30 | +import org.thingsboard.server.common.data.EntityView; | |
31 | +import org.thingsboard.server.common.data.id.CustomerId; | |
32 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | |
33 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
34 | +import org.thingsboard.server.common.data.id.TenantId; | |
35 | +import org.thingsboard.server.common.data.objects.TelemetryEntityView; | |
36 | +import org.thingsboard.server.dao.model.ModelConstants; | |
37 | +import org.thingsboard.server.dao.model.SearchTextEntity; | |
38 | + | |
39 | +import javax.persistence.EnumType; | |
40 | +import javax.persistence.Enumerated; | |
41 | +import java.io.IOException; | |
42 | +import java.util.UUID; | |
43 | + | |
44 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; | |
45 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME; | |
46 | +import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; | |
47 | + | |
48 | +/** | |
49 | + * Created by Victor Basanets on 8/31/2017. | |
50 | + */ | |
51 | +@Data | |
52 | +@Table(name = ENTITY_VIEW_TABLE_FAMILY_NAME) | |
53 | +@EqualsAndHashCode | |
54 | +@ToString | |
55 | +@Slf4j | |
56 | +public class EntityViewEntity implements SearchTextEntity<EntityView> { | |
57 | + | |
58 | + @PartitionKey(value = 0) | |
59 | + @Column(name = ID_PROPERTY) | |
60 | + private UUID id; | |
61 | + | |
62 | + @Enumerated(EnumType.STRING) | |
63 | + @Column(name = ENTITY_TYPE_PROPERTY) | |
64 | + private EntityType entityType; | |
65 | + | |
66 | + @PartitionKey(value = 1) | |
67 | + @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY) | |
68 | + private UUID tenantId; | |
69 | + | |
70 | + @PartitionKey(value = 2) | |
71 | + @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY) | |
72 | + private UUID customerId; | |
73 | + | |
74 | + @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) | |
75 | + private UUID entityId; | |
76 | + | |
77 | + @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) | |
78 | + private String name; | |
79 | + | |
80 | + @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY) | |
81 | + private String keys; | |
82 | + | |
83 | + @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY) | |
84 | + private long startTs; | |
85 | + | |
86 | + @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY) | |
87 | + private long endTs; | |
88 | + | |
89 | + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) | |
90 | + private String searchText; | |
91 | + | |
92 | + @Type(type = "json") | |
93 | + @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) | |
94 | + private JsonNode additionalInfo; | |
95 | + | |
96 | + private static final ObjectMapper mapper = new ObjectMapper(); | |
97 | + | |
98 | + public EntityViewEntity() { | |
99 | + super(); | |
100 | + } | |
101 | + | |
102 | + public EntityViewEntity(EntityView entityView) { | |
103 | + if (entityView.getId() != null) { | |
104 | + this.id = entityView.getId().getId(); | |
105 | + } | |
106 | + if (entityView.getEntityId() != null) { | |
107 | + this.entityId = entityView.getEntityId().getId(); | |
108 | + this.entityType = entityView.getEntityId().getEntityType(); | |
109 | + } | |
110 | + if (entityView.getTenantId() != null) { | |
111 | + this.tenantId = entityView.getTenantId().getId(); | |
112 | + } | |
113 | + if (entityView.getCustomerId() != null) { | |
114 | + this.customerId = entityView.getCustomerId().getId(); | |
115 | + } | |
116 | + this.name = entityView.getName(); | |
117 | + try { | |
118 | + this.keys = mapper.writeValueAsString(entityView.getKeys()); | |
119 | + } catch (IOException e) { | |
120 | + log.error("Unable to serialize entity view keys!", e); | |
121 | + } | |
122 | + this.startTs = entityView.getStartTimeMs(); | |
123 | + this.endTs = entityView.getEndTimeMs(); | |
124 | + this.searchText = entityView.getSearchText(); | |
125 | + this.additionalInfo = entityView.getAdditionalInfo(); | |
126 | + } | |
127 | + | |
128 | + @Override | |
129 | + public String getSearchTextSource() { | |
130 | + return name; | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public EntityView toData() { | |
135 | + EntityView entityView = new EntityView(new EntityViewId(id)); | |
136 | + entityView.setCreatedTime(UUIDs.unixTimestamp(id)); | |
137 | + if (entityId != null) { | |
138 | + entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), entityId.toString())); | |
139 | + } | |
140 | + if (tenantId != null) { | |
141 | + entityView.setTenantId(new TenantId(tenantId)); | |
142 | + } | |
143 | + if (customerId != null) { | |
144 | + entityView.setCustomerId(new CustomerId(customerId)); | |
145 | + } | |
146 | + entityView.setName(name); | |
147 | + try { | |
148 | + entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); | |
149 | + } catch (IOException e) { | |
150 | + log.error("Unable to read entity view keys!", e); | |
151 | + } | |
152 | + entityView.setStartTimeMs(startTs); | |
153 | + entityView.setEndTimeMs(endTs); | |
154 | + entityView.setAdditionalInfo(additionalInfo); | |
155 | + return entityView; | |
156 | + } | |
157 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.model.sql; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.fasterxml.jackson.databind.JsonNode; | |
20 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
21 | +import lombok.Data; | |
22 | +import lombok.EqualsAndHashCode; | |
23 | +import lombok.extern.slf4j.Slf4j; | |
24 | +import org.hibernate.annotations.Type; | |
25 | +import org.hibernate.annotations.TypeDef; | |
26 | +import org.thingsboard.server.common.data.EntityType; | |
27 | +import org.thingsboard.server.common.data.EntityView; | |
28 | +import org.thingsboard.server.common.data.id.CustomerId; | |
29 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | |
30 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
31 | +import org.thingsboard.server.common.data.id.TenantId; | |
32 | +import org.thingsboard.server.common.data.objects.TelemetryEntityView; | |
33 | +import org.thingsboard.server.dao.model.BaseSqlEntity; | |
34 | +import org.thingsboard.server.dao.model.ModelConstants; | |
35 | +import org.thingsboard.server.dao.model.SearchTextEntity; | |
36 | +import org.thingsboard.server.dao.util.mapping.JsonStringType; | |
37 | + | |
38 | +import javax.persistence.Column; | |
39 | +import javax.persistence.Entity; | |
40 | +import javax.persistence.EnumType; | |
41 | +import javax.persistence.Enumerated; | |
42 | +import javax.persistence.Table; | |
43 | +import java.io.IOException; | |
44 | + | |
45 | +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; | |
46 | + | |
47 | +/** | |
48 | + * Created by Victor Basanets on 8/30/2017. | |
49 | + */ | |
50 | + | |
51 | +@Data | |
52 | +@EqualsAndHashCode(callSuper = true) | |
53 | +@Entity | |
54 | +@TypeDef(name = "json", typeClass = JsonStringType.class) | |
55 | +@Table(name = ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME) | |
56 | +@Slf4j | |
57 | +public class EntityViewEntity extends BaseSqlEntity<EntityView> implements SearchTextEntity<EntityView> { | |
58 | + | |
59 | + @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) | |
60 | + private String entityId; | |
61 | + | |
62 | + @Enumerated(EnumType.STRING) | |
63 | + @Column(name = ENTITY_TYPE_PROPERTY) | |
64 | + private EntityType entityType; | |
65 | + | |
66 | + @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY) | |
67 | + private String tenantId; | |
68 | + | |
69 | + @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY) | |
70 | + private String customerId; | |
71 | + | |
72 | + @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) | |
73 | + private String name; | |
74 | + | |
75 | + @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY) | |
76 | + private String keys; | |
77 | + | |
78 | + @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY) | |
79 | + private long startTs; | |
80 | + | |
81 | + @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY) | |
82 | + private long endTs; | |
83 | + | |
84 | + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) | |
85 | + private String searchText; | |
86 | + | |
87 | + @Type(type = "json") | |
88 | + @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) | |
89 | + private JsonNode additionalInfo; | |
90 | + | |
91 | + private static final ObjectMapper mapper = new ObjectMapper(); | |
92 | + | |
93 | + public EntityViewEntity() { | |
94 | + super(); | |
95 | + } | |
96 | + | |
97 | + public EntityViewEntity(EntityView entityView) { | |
98 | + if (entityView.getId() != null) { | |
99 | + this.setId(entityView.getId().getId()); | |
100 | + } | |
101 | + if (entityView.getEntityId() != null) { | |
102 | + this.entityId = toString(entityView.getEntityId().getId()); | |
103 | + this.entityType = entityView.getEntityId().getEntityType(); | |
104 | + } | |
105 | + if (entityView.getTenantId() != null) { | |
106 | + this.tenantId = toString(entityView.getTenantId().getId()); | |
107 | + } | |
108 | + if (entityView.getCustomerId() != null) { | |
109 | + this.customerId = toString(entityView.getCustomerId().getId()); | |
110 | + } | |
111 | + this.name = entityView.getName(); | |
112 | + try { | |
113 | + this.keys = mapper.writeValueAsString(entityView.getKeys()); | |
114 | + } catch (IOException e) { | |
115 | + log.error("Unable to serialize entity view keys!", e); | |
116 | + } | |
117 | + this.startTs = entityView.getStartTimeMs(); | |
118 | + this.endTs = entityView.getEndTimeMs(); | |
119 | + this.searchText = entityView.getSearchText(); | |
120 | + this.additionalInfo = entityView.getAdditionalInfo(); | |
121 | + } | |
122 | + | |
123 | + @Override | |
124 | + public String getSearchTextSource() { | |
125 | + return name; | |
126 | + } | |
127 | + | |
128 | + @Override | |
129 | + public void setSearchText(String searchText) { | |
130 | + this.searchText = searchText; | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public EntityView toData() { | |
135 | + EntityView entityView = new EntityView(new EntityViewId(getId())); | |
136 | + entityView.setCreatedTime(UUIDs.unixTimestamp(getId())); | |
137 | + | |
138 | + if (entityId != null) { | |
139 | + entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); | |
140 | + } | |
141 | + if (tenantId != null) { | |
142 | + entityView.setTenantId(new TenantId(toUUID(tenantId))); | |
143 | + } | |
144 | + if (customerId != null) { | |
145 | + entityView.setCustomerId(new CustomerId(toUUID(customerId))); | |
146 | + } | |
147 | + entityView.setName(name); | |
148 | + try { | |
149 | + entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); | |
150 | + } catch (IOException e) { | |
151 | + log.error("Unable to read entity view keys!", e); | |
152 | + } | |
153 | + entityView.setStartTimeMs(startTs); | |
154 | + entityView.setEndTimeMs(endTs); | |
155 | + entityView.setAdditionalInfo(additionalInfo); | |
156 | + return entityView; | |
157 | + } | |
158 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.sql.entityview; | |
17 | + | |
18 | +import org.springframework.data.domain.Pageable; | |
19 | +import org.springframework.data.jpa.repository.Query; | |
20 | +import org.springframework.data.repository.CrudRepository; | |
21 | +import org.springframework.data.repository.query.Param; | |
22 | +import org.thingsboard.server.common.data.EntityView; | |
23 | +import org.thingsboard.server.common.data.id.EntityId; | |
24 | +import org.thingsboard.server.dao.model.sql.EntityViewEntity; | |
25 | +import org.thingsboard.server.dao.util.SqlDao; | |
26 | + | |
27 | +import java.util.List; | |
28 | + | |
29 | +/** | |
30 | + * Created by Victor Basanets on 8/31/2017. | |
31 | + */ | |
32 | +@SqlDao | |
33 | +public interface EntityViewRepository extends CrudRepository<EntityViewEntity, String> { | |
34 | + | |
35 | + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + | |
36 | + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + | |
37 | + "AND e.id > :idOffset ORDER BY e.id") | |
38 | + List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId, | |
39 | + @Param("textSearch") String textSearch, | |
40 | + @Param("idOffset") String idOffset, | |
41 | + Pageable pageable); | |
42 | + | |
43 | + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + | |
44 | + "AND e.customerId = :customerId " + | |
45 | + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
46 | + "AND e.id > :idOffset ORDER BY e.id") | |
47 | + List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, | |
48 | + @Param("customerId") String customerId, | |
49 | + @Param("searchText") String searchText, | |
50 | + @Param("idOffset") String idOffset, | |
51 | + Pageable pageable); | |
52 | + | |
53 | + EntityViewEntity findByTenantIdAndName(String tenantId, String name); | |
54 | + | |
55 | + List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId); | |
56 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.sql.entityview; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import org.springframework.beans.factory.annotation.Autowired; | |
20 | +import org.springframework.data.domain.PageRequest; | |
21 | +import org.springframework.data.repository.CrudRepository; | |
22 | +import org.springframework.stereotype.Component; | |
23 | +import org.thingsboard.server.common.data.EntitySubtype; | |
24 | +import org.thingsboard.server.common.data.EntityType; | |
25 | +import org.thingsboard.server.common.data.EntityView; | |
26 | +import org.thingsboard.server.common.data.UUIDConverter; | |
27 | +import org.thingsboard.server.common.data.id.EntityId; | |
28 | +import org.thingsboard.server.common.data.id.TenantId; | |
29 | +import org.thingsboard.server.common.data.page.TextPageLink; | |
30 | +import org.thingsboard.server.dao.DaoUtil; | |
31 | +import org.thingsboard.server.dao.entityview.EntityViewDao; | |
32 | +import org.thingsboard.server.dao.model.sql.EntityViewEntity; | |
33 | +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; | |
34 | +import org.thingsboard.server.dao.util.SqlDao; | |
35 | + | |
36 | +import java.util.ArrayList; | |
37 | +import java.util.Collections; | |
38 | +import java.util.List; | |
39 | +import java.util.Objects; | |
40 | +import java.util.Optional; | |
41 | +import java.util.UUID; | |
42 | + | |
43 | +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | |
44 | +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs; | |
45 | +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; | |
46 | + | |
47 | +/** | |
48 | + * Created by Victor Basanets on 8/31/2017. | |
49 | + */ | |
50 | +@Component | |
51 | +@SqlDao | |
52 | +public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity, EntityView> | |
53 | + implements EntityViewDao { | |
54 | + | |
55 | + @Autowired | |
56 | + private EntityViewRepository entityViewRepository; | |
57 | + | |
58 | + @Override | |
59 | + protected Class<EntityViewEntity> getEntityClass() { | |
60 | + return EntityViewEntity.class; | |
61 | + } | |
62 | + | |
63 | + @Override | |
64 | + protected CrudRepository<EntityViewEntity, String> getCrudRepository() { | |
65 | + return entityViewRepository; | |
66 | + } | |
67 | + | |
68 | + @Override | |
69 | + public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) { | |
70 | + return DaoUtil.convertDataList( | |
71 | + entityViewRepository.findByTenantId( | |
72 | + fromTimeUUID(tenantId), | |
73 | + Objects.toString(pageLink.getTextSearch(), ""), | |
74 | + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), | |
75 | + new PageRequest(0, pageLink.getLimit()))); | |
76 | + } | |
77 | + | |
78 | + @Override | |
79 | + public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) { | |
80 | + return Optional.ofNullable( | |
81 | + DaoUtil.getData(entityViewRepository.findByTenantIdAndName(fromTimeUUID(tenantId), name))); | |
82 | + } | |
83 | + | |
84 | + @Override | |
85 | + public List<EntityView> findEntityViewsByTenantIdAndCustomerId(UUID tenantId, | |
86 | + UUID customerId, | |
87 | + TextPageLink pageLink) { | |
88 | + return DaoUtil.convertDataList( | |
89 | + entityViewRepository.findByTenantIdAndCustomerId( | |
90 | + fromTimeUUID(tenantId), | |
91 | + fromTimeUUID(customerId), | |
92 | + Objects.toString(pageLink.getTextSearch(), ""), | |
93 | + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), | |
94 | + new PageRequest(0, pageLink.getLimit()) | |
95 | + )); | |
96 | + } | |
97 | + | |
98 | + @Override | |
99 | + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { | |
100 | + return service.submit(() -> DaoUtil.convertDataList( | |
101 | + entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId)))); | |
102 | + } | |
103 | +} | ... | ... |
... | ... | @@ -44,6 +44,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; |
44 | 44 | import org.thingsboard.server.dao.timeseries.TimeseriesDao; |
45 | 45 | import org.thingsboard.server.dao.timeseries.TsInsertExecutorType; |
46 | 46 | import org.thingsboard.server.dao.util.SqlDao; |
47 | +import org.thingsboard.server.dao.util.SqlTsDao; | |
47 | 48 | |
48 | 49 | import javax.annotation.Nullable; |
49 | 50 | import javax.annotation.PostConstruct; |
... | ... | @@ -60,7 +61,7 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
60 | 61 | |
61 | 62 | @Component |
62 | 63 | @Slf4j |
63 | -@SqlDao | |
64 | +@SqlTsDao | |
64 | 65 | public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { |
65 | 66 | |
66 | 67 | @Value("${sql.ts_inserts_executor_type}") | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.customer.CustomerService; |
29 | 29 | import org.thingsboard.server.dao.dashboard.DashboardService; |
30 | 30 | import org.thingsboard.server.dao.device.DeviceService; |
31 | 31 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
32 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
32 | 33 | import org.thingsboard.server.dao.exception.DataValidationException; |
33 | 34 | import org.thingsboard.server.dao.rule.RuleChainService; |
34 | 35 | import org.thingsboard.server.dao.service.DataValidator; |
... | ... | @@ -64,6 +65,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe |
64 | 65 | private DeviceService deviceService; |
65 | 66 | |
66 | 67 | @Autowired |
68 | + private EntityViewService entityViewService; | |
69 | + | |
70 | + @Autowired | |
67 | 71 | private WidgetsBundleService widgetsBundleService; |
68 | 72 | |
69 | 73 | @Autowired |
... | ... | @@ -101,6 +105,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe |
101 | 105 | customerService.deleteCustomersByTenantId(tenantId); |
102 | 106 | widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); |
103 | 107 | dashboardService.deleteDashboardsByTenantId(tenantId); |
108 | + entityViewService.deleteEntityViewsByTenantId(tenantId); | |
104 | 109 | assetService.deleteAssetsByTenantId(tenantId); |
105 | 110 | deviceService.deleteDevicesByTenantId(tenantId); |
106 | 111 | userService.deleteTenantAdmins(tenantId); | ... | ... |
... | ... | @@ -21,15 +21,22 @@ import com.google.common.util.concurrent.ListenableFuture; |
21 | 21 | import lombok.extern.slf4j.Slf4j; |
22 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | 23 | import org.springframework.stereotype.Service; |
24 | +import org.thingsboard.server.common.data.EntityType; | |
25 | +import org.thingsboard.server.common.data.EntityView; | |
24 | 26 | import org.thingsboard.server.common.data.id.EntityId; |
27 | +import org.thingsboard.server.common.data.id.EntityViewId; | |
28 | +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; | |
25 | 29 | import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; |
26 | 30 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
27 | 31 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
32 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
28 | 33 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
29 | 34 | import org.thingsboard.server.dao.service.Validator; |
30 | 35 | |
36 | +import java.util.ArrayList; | |
31 | 37 | import java.util.Collection; |
32 | 38 | import java.util.List; |
39 | +import java.util.stream.Collectors; | |
33 | 40 | |
34 | 41 | import static org.apache.commons.lang3.StringUtils.isBlank; |
35 | 42 | |
... | ... | @@ -46,10 +53,21 @@ public class BaseTimeseriesService implements TimeseriesService { |
46 | 53 | @Autowired |
47 | 54 | private TimeseriesDao timeseriesDao; |
48 | 55 | |
56 | + @Autowired | |
57 | + private EntityViewService entityViewService; | |
58 | + | |
49 | 59 | @Override |
50 | 60 | public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) { |
51 | 61 | validate(entityId); |
52 | 62 | queries.forEach(BaseTimeseriesService::validate); |
63 | + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { | |
64 | + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); | |
65 | + List<ReadTsKvQuery> filteredQueries = | |
66 | + queries.stream() | |
67 | + .filter(query -> entityView.getKeys().getTimeseries().isEmpty() || entityView.getKeys().getTimeseries().contains(query.getKey())) | |
68 | + .collect(Collectors.toList()); | |
69 | + return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, filteredQueries)); | |
70 | + } | |
53 | 71 | return timeseriesDao.findAllAsync(entityId, queries); |
54 | 72 | } |
55 | 73 | |
... | ... | @@ -58,6 +76,19 @@ public class BaseTimeseriesService implements TimeseriesService { |
58 | 76 | validate(entityId); |
59 | 77 | List<ListenableFuture<TsKvEntry>> futures = Lists.newArrayListWithExpectedSize(keys.size()); |
60 | 78 | keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key)); |
79 | + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { | |
80 | + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); | |
81 | + List<String> filteredKeys = new ArrayList<>(keys); | |
82 | + if (!entityView.getKeys().getTimeseries().isEmpty()) { | |
83 | + filteredKeys.retainAll(entityView.getKeys().getTimeseries()); | |
84 | + } | |
85 | + List<ReadTsKvQuery> queries = | |
86 | + filteredKeys.stream() | |
87 | + .map(key -> new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), entityView.getEndTimeMs(), 1, "ASC")) | |
88 | + .collect(Collectors.toList()); | |
89 | + | |
90 | + return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries)); | |
91 | + } | |
61 | 92 | keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key))); |
62 | 93 | return Futures.allAsList(futures); |
63 | 94 | } |
... | ... | @@ -92,11 +123,24 @@ public class BaseTimeseriesService implements TimeseriesService { |
92 | 123 | } |
93 | 124 | |
94 | 125 | private void saveAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { |
126 | + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { | |
127 | + throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Only read only"); | |
128 | + } | |
95 | 129 | futures.add(timeseriesDao.savePartition(entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); |
96 | 130 | futures.add(timeseriesDao.saveLatest(entityId, tsKvEntry)); |
97 | 131 | futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl)); |
98 | 132 | } |
99 | 133 | |
134 | + private List<ReadTsKvQuery> updateQueriesForEntityView(EntityView entityView, List<ReadTsKvQuery> queries) { | |
135 | + return queries.stream().map(query -> { | |
136 | + long startTs = entityView.getStartTimeMs() == 0 ? query.getStartTs() : entityView.getStartTimeMs(); | |
137 | + long endTs = entityView.getEndTimeMs() == 0 ? query.getEndTs() : entityView.getEndTimeMs(); | |
138 | + | |
139 | + return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query : | |
140 | + new BaseReadTsKvQuery(query.getKey(), startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation()); | |
141 | + }).collect(Collectors.toList()); | |
142 | + } | |
143 | + | |
100 | 144 | @Override |
101 | 145 | public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) { |
102 | 146 | validate(entityId); | ... | ... |
... | ... | @@ -49,6 +49,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; |
49 | 49 | import org.thingsboard.server.dao.model.ModelConstants; |
50 | 50 | import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao; |
51 | 51 | import org.thingsboard.server.dao.util.NoSqlDao; |
52 | +import org.thingsboard.server.dao.util.NoSqlTsDao; | |
52 | 53 | |
53 | 54 | import javax.annotation.Nullable; |
54 | 55 | import javax.annotation.PostConstruct; |
... | ... | @@ -70,7 +71,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; |
70 | 71 | */ |
71 | 72 | @Component |
72 | 73 | @Slf4j |
73 | -@NoSqlDao | |
74 | +@NoSqlTsDao | |
74 | 75 | public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implements TimeseriesDao { |
75 | 76 | |
76 | 77 | private static final int MIN_AGGREGATION_STEP_MS = 1000; | ... | ... |
... | ... | @@ -34,7 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger; |
34 | 34 | |
35 | 35 | @Component |
36 | 36 | @Slf4j |
37 | -@NoSqlDao | |
37 | +@NoSqlAnyDao | |
38 | 38 | public class BufferedRateLimiter implements AsyncRateLimiter { |
39 | 39 | |
40 | 40 | private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.util; | |
17 | + | |
18 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | |
19 | + | |
20 | +@ConditionalOnExpression("'${database.ts.type}'=='cassandra' || '${database.entities.type}'=='cassandra'") | |
21 | +public @interface NoSqlAnyDao { | |
22 | +} | ... | ... |
... | ... | @@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util; |
17 | 17 | |
18 | 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
19 | 19 | |
20 | -@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "cassandra") | |
20 | +@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "cassandra") | |
21 | 21 | public @interface NoSqlDao { |
22 | 22 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.util; | |
17 | + | |
18 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
19 | + | |
20 | +@ConditionalOnProperty(prefix = "database.ts", value = "type", havingValue = "cassandra") | |
21 | +public @interface NoSqlTsDao { | |
22 | +} | ... | ... |
... | ... | @@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util; |
17 | 17 | |
18 | 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
19 | 19 | |
20 | -@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "sql") | |
20 | +@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql") | |
21 | 21 | public @interface SqlDao { |
22 | 22 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.util; | |
17 | + | |
18 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
19 | + | |
20 | +@ConditionalOnProperty(prefix = "database.ts", value = "type", havingValue = "sql") | |
21 | +public @interface SqlTsDao { | |
22 | +} | ... | ... |
dao/src/main/resources/cassandra/schema-entities.cql
renamed from
dao/src/main/resources/cassandra/schema.cql
... | ... | @@ -165,35 +165,55 @@ CREATE TABLE IF NOT EXISTS thingsboard.device ( |
165 | 165 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS |
166 | 166 | SELECT * |
167 | 167 | from thingsboard.device |
168 | - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL | |
168 | + WHERE tenant_id IS NOT NULL | |
169 | + AND customer_id IS NOT NULL | |
170 | + AND type IS NOT NULL | |
171 | + AND name IS NOT NULL | |
172 | + AND id IS NOT NULL | |
169 | 173 | PRIMARY KEY ( tenant_id, name, id, customer_id, type) |
170 | 174 | WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC); |
171 | 175 | |
172 | 176 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS |
173 | 177 | SELECT * |
174 | 178 | from thingsboard.device |
175 | - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL | |
179 | + WHERE tenant_id IS NOT NULL | |
180 | + AND customer_id IS NOT NULL | |
181 | + AND type IS NOT NULL | |
182 | + AND search_text IS NOT NULL | |
183 | + AND id IS NOT NULL | |
176 | 184 | PRIMARY KEY ( tenant_id, search_text, id, customer_id, type) |
177 | 185 | WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC); |
178 | 186 | |
179 | 187 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS |
180 | 188 | SELECT * |
181 | 189 | from thingsboard.device |
182 | - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL | |
190 | + WHERE tenant_id IS NOT NULL | |
191 | + AND customer_id IS NOT NULL | |
192 | + AND type IS NOT NULL | |
193 | + AND search_text IS NOT NULL | |
194 | + AND id IS NOT NULL | |
183 | 195 | PRIMARY KEY ( tenant_id, type, search_text, id, customer_id) |
184 | 196 | WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC); |
185 | 197 | |
186 | 198 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS |
187 | 199 | SELECT * |
188 | 200 | from thingsboard.device |
189 | - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL | |
201 | + WHERE tenant_id IS NOT NULL | |
202 | + AND customer_id IS NOT NULL | |
203 | + AND type IS NOT NULL | |
204 | + AND search_text IS NOT NULL | |
205 | + AND id IS NOT NULL | |
190 | 206 | PRIMARY KEY ( customer_id, tenant_id, search_text, id, type ) |
191 | 207 | WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); |
192 | 208 | |
193 | 209 | CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS |
194 | 210 | SELECT * |
195 | 211 | from thingsboard.device |
196 | - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL | |
212 | + WHERE tenant_id IS NOT NULL | |
213 | + AND customer_id IS NOT NULL | |
214 | + AND type IS NOT NULL | |
215 | + AND search_text IS NOT NULL | |
216 | + AND id IS NOT NULL | |
197 | 217 | PRIMARY KEY ( customer_id, tenant_id, type, search_text, id ) |
198 | 218 | WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC ); |
199 | 219 | |
... | ... | @@ -378,41 +398,6 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_searc |
378 | 398 | PRIMARY KEY ( tenant_id, search_text, id ) |
379 | 399 | WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); |
380 | 400 | |
381 | -CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( | |
382 | - entity_type text, // (DEVICE, CUSTOMER, TENANT) | |
383 | - entity_id timeuuid, | |
384 | - key text, | |
385 | - partition bigint, | |
386 | - ts bigint, | |
387 | - bool_v boolean, | |
388 | - str_v text, | |
389 | - long_v bigint, | |
390 | - dbl_v double, | |
391 | - PRIMARY KEY (( entity_type, entity_id, key, partition ), ts) | |
392 | -); | |
393 | - | |
394 | -CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf ( | |
395 | - entity_type text, // (DEVICE, CUSTOMER, TENANT) | |
396 | - entity_id timeuuid, | |
397 | - key text, | |
398 | - partition bigint, | |
399 | - PRIMARY KEY (( entity_type, entity_id, key ), partition) | |
400 | -) WITH CLUSTERING ORDER BY ( partition ASC ) | |
401 | - AND compaction = { 'class' : 'LeveledCompactionStrategy' }; | |
402 | - | |
403 | -CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf ( | |
404 | - entity_type text, // (DEVICE, CUSTOMER, TENANT) | |
405 | - entity_id timeuuid, | |
406 | - key text, | |
407 | - ts bigint, | |
408 | - bool_v boolean, | |
409 | - str_v text, | |
410 | - long_v bigint, | |
411 | - dbl_v double, | |
412 | - PRIMARY KEY (( entity_type, entity_id ), key) | |
413 | -) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; | |
414 | - | |
415 | - | |
416 | 401 | CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf ( |
417 | 402 | entity_type text, // (DEVICE, CUSTOMER, TENANT) |
418 | 403 | entity_id timeuuid, |
... | ... | @@ -638,3 +623,62 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( |
638 | 623 | additional_info text, |
639 | 624 | PRIMARY KEY (id) |
640 | 625 | ); |
626 | + | |
627 | +CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( | |
628 | + id timeuuid, | |
629 | + entity_id timeuuid, | |
630 | + entity_type text, | |
631 | + tenant_id timeuuid, | |
632 | + customer_id timeuuid, | |
633 | + name text, | |
634 | + keys text, | |
635 | + start_ts bigint, | |
636 | + end_ts bigint, | |
637 | + search_text text, | |
638 | + additional_info text, | |
639 | + PRIMARY KEY (id, entity_id, tenant_id, customer_id) | |
640 | +); | |
641 | + | |
642 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS | |
643 | + SELECT * | |
644 | + from thingsboard.entity_views | |
645 | + WHERE tenant_id IS NOT NULL | |
646 | + AND entity_id IS NOT NULL | |
647 | + AND customer_id IS NOT NULL | |
648 | + AND name IS NOT NULL | |
649 | + AND id IS NOT NULL | |
650 | + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id) | |
651 | + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); | |
652 | + | |
653 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS | |
654 | + SELECT * | |
655 | + from thingsboard.entity_views | |
656 | + WHERE tenant_id IS NOT NULL | |
657 | + AND entity_id IS NOT NULL | |
658 | + AND customer_id IS NOT NULL | |
659 | + AND search_text IS NOT NULL | |
660 | + AND id IS NOT NULL | |
661 | + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id) | |
662 | + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); | |
663 | + | |
664 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS | |
665 | + SELECT * | |
666 | + from thingsboard.entity_views | |
667 | + WHERE tenant_id IS NOT NULL | |
668 | + AND customer_id IS NOT NULL | |
669 | + AND entity_id IS NOT NULL | |
670 | + AND search_text IS NOT NULL | |
671 | + AND id IS NOT NULL | |
672 | + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id) | |
673 | + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); | |
674 | + | |
675 | +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS | |
676 | + SELECT * | |
677 | + from thingsboard.entity_views | |
678 | + WHERE tenant_id IS NOT NULL | |
679 | + AND customer_id IS NOT NULL | |
680 | + AND entity_id IS NOT NULL | |
681 | + AND search_text IS NOT NULL | |
682 | + AND id IS NOT NULL | |
683 | + PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id) | |
684 | + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); | |
\ No newline at end of file | ... | ... |
1 | +-- | |
2 | +-- Copyright © 2016-2018 The Thingsboard Authors | |
3 | +-- | |
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +-- you may not use this file except in compliance with the License. | |
6 | +-- You may obtain a copy of the License at | |
7 | +-- | |
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +-- | |
10 | +-- Unless required by applicable law or agreed to in writing, software | |
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +-- See the License for the specific language governing permissions and | |
14 | +-- limitations under the License. | |
15 | +-- | |
16 | + | |
17 | +CREATE KEYSPACE IF NOT EXISTS thingsboard | |
18 | +WITH replication = { | |
19 | + 'class' : 'SimpleStrategy', | |
20 | + 'replication_factor' : 1 | |
21 | +}; | |
22 | + | |
23 | +CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( | |
24 | + entity_type text, // (DEVICE, CUSTOMER, TENANT) | |
25 | + entity_id timeuuid, | |
26 | + key text, | |
27 | + partition bigint, | |
28 | + ts bigint, | |
29 | + bool_v boolean, | |
30 | + str_v text, | |
31 | + long_v bigint, | |
32 | + dbl_v double, | |
33 | + PRIMARY KEY (( entity_type, entity_id, key, partition ), ts) | |
34 | +); | |
35 | + | |
36 | +CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf ( | |
37 | + entity_type text, // (DEVICE, CUSTOMER, TENANT) | |
38 | + entity_id timeuuid, | |
39 | + key text, | |
40 | + partition bigint, | |
41 | + PRIMARY KEY (( entity_type, entity_id, key ), partition) | |
42 | +) WITH CLUSTERING ORDER BY ( partition ASC ) | |
43 | + AND compaction = { 'class' : 'LeveledCompactionStrategy' }; | |
44 | + | |
45 | +CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf ( | |
46 | + entity_type text, // (DEVICE, CUSTOMER, TENANT) | |
47 | + entity_id timeuuid, | |
48 | + key text, | |
49 | + ts bigint, | |
50 | + bool_v boolean, | |
51 | + str_v text, | |
52 | + long_v bigint, | |
53 | + dbl_v double, | |
54 | + PRIMARY KEY (( entity_type, entity_id ), key) | |
55 | +) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; | ... | ... |
dao/src/main/resources/sql/schema-entities.sql
renamed from
dao/src/main/resources/sql/schema.sql
... | ... | @@ -179,30 +179,6 @@ CREATE TABLE IF NOT EXISTS tenant ( |
179 | 179 | zip varchar(255) |
180 | 180 | ); |
181 | 181 | |
182 | -CREATE TABLE IF NOT EXISTS ts_kv ( | |
183 | - entity_type varchar(255) NOT NULL, | |
184 | - entity_id varchar(31) NOT NULL, | |
185 | - key varchar(255) NOT NULL, | |
186 | - ts bigint NOT NULL, | |
187 | - bool_v boolean, | |
188 | - str_v varchar(10000000), | |
189 | - long_v bigint, | |
190 | - dbl_v double precision, | |
191 | - CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts) | |
192 | -); | |
193 | - | |
194 | -CREATE TABLE IF NOT EXISTS ts_kv_latest ( | |
195 | - entity_type varchar(255) NOT NULL, | |
196 | - entity_id varchar(31) NOT NULL, | |
197 | - key varchar(255) NOT NULL, | |
198 | - ts bigint NOT NULL, | |
199 | - bool_v boolean, | |
200 | - str_v varchar(10000000), | |
201 | - long_v bigint, | |
202 | - dbl_v double precision, | |
203 | - CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key) | |
204 | -); | |
205 | - | |
206 | 182 | CREATE TABLE IF NOT EXISTS user_credentials ( |
207 | 183 | id varchar(31) NOT NULL CONSTRAINT user_credentials_pkey PRIMARY KEY, |
208 | 184 | activate_token varchar(255) UNIQUE, |
... | ... | @@ -251,3 +227,17 @@ CREATE TABLE IF NOT EXISTS rule_node ( |
251 | 227 | debug_mode boolean, |
252 | 228 | search_text varchar(255) |
253 | 229 | ); |
230 | + | |
231 | +CREATE TABLE IF NOT EXISTS entity_views ( | |
232 | + id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY, | |
233 | + entity_id varchar(31), | |
234 | + entity_type varchar(255), | |
235 | + tenant_id varchar(31), | |
236 | + customer_id varchar(31), | |
237 | + name varchar(255), | |
238 | + keys varchar(255), | |
239 | + start_ts bigint, | |
240 | + end_ts bigint, | |
241 | + search_text varchar(255), | |
242 | + additional_info varchar | |
243 | +); | ... | ... |
dao/src/main/resources/sql/schema-ts.sql
0 → 100644
1 | +-- | |
2 | +-- Copyright © 2016-2018 The Thingsboard Authors | |
3 | +-- | |
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +-- you may not use this file except in compliance with the License. | |
6 | +-- You may obtain a copy of the License at | |
7 | +-- | |
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +-- | |
10 | +-- Unless required by applicable law or agreed to in writing, software | |
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +-- See the License for the specific language governing permissions and | |
14 | +-- limitations under the License. | |
15 | +-- | |
16 | + | |
17 | +CREATE TABLE IF NOT EXISTS ts_kv ( | |
18 | + entity_type varchar(255) NOT NULL, | |
19 | + entity_id varchar(31) NOT NULL, | |
20 | + key varchar(255) NOT NULL, | |
21 | + ts bigint NOT NULL, | |
22 | + bool_v boolean, | |
23 | + str_v varchar(10000000), | |
24 | + long_v bigint, | |
25 | + dbl_v double precision, | |
26 | + CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts) | |
27 | +); | |
28 | + | |
29 | +CREATE TABLE IF NOT EXISTS ts_kv_latest ( | |
30 | + entity_type varchar(255) NOT NULL, | |
31 | + entity_id varchar(31) NOT NULL, | |
32 | + key varchar(255) NOT NULL, | |
33 | + ts bigint NOT NULL, | |
34 | + bool_v boolean, | |
35 | + str_v varchar(10000000), | |
36 | + long_v bigint, | |
37 | + dbl_v double precision, | |
38 | + CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key) | |
39 | +); | ... | ... |
... | ... | @@ -30,7 +30,7 @@ public class JpaDaoTestSuite { |
30 | 30 | |
31 | 31 | @ClassRule |
32 | 32 | public static CustomSqlUnit sqlUnit = new CustomSqlUnit( |
33 | - Arrays.asList("sql/schema.sql", "sql/system-data.sql"), | |
33 | + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), | |
34 | 34 | "sql/drop-all-tables.sql", |
35 | 35 | "sql-test.properties" |
36 | 36 | ); | ... | ... |
... | ... | @@ -34,7 +34,9 @@ public class NoSqlDaoServiceTestSuite { |
34 | 34 | @ClassRule |
35 | 35 | public static CustomCassandraCQLUnit cassandraUnit = |
36 | 36 | new CustomCassandraCQLUnit( |
37 | - Arrays.asList(new ClassPathCQLDataSet("cassandra/schema.cql", false, false), | |
37 | + Arrays.asList( | |
38 | + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), | |
39 | + new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), | |
38 | 40 | new ClassPathCQLDataSet("cassandra/system-data.cql", false, false), |
39 | 41 | new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)), |
40 | 42 | "cassandra-test.yaml", 30000L); | ... | ... |
... | ... | @@ -30,7 +30,7 @@ public class SqlDaoServiceTestSuite { |
30 | 30 | |
31 | 31 | @ClassRule |
32 | 32 | public static CustomSqlUnit sqlUnit = new CustomSqlUnit( |
33 | - Arrays.asList("sql/schema.sql", "sql/system-data.sql", "sql/system-test.sql"), | |
33 | + Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql", "sql/system-test.sql"), | |
34 | 34 | "sql/drop-all-tables.sql", |
35 | 35 | "sql-test.properties" |
36 | 36 | ); | ... | ... |
... | ... | @@ -24,6 +24,9 @@ caffeine.specs.devices.maxSize=100000 |
24 | 24 | caffeine.specs.assets.timeToLiveInMinutes=1440 |
25 | 25 | caffeine.specs.assets.maxSize=100000 |
26 | 26 | |
27 | +caffeine.specs.entityViews.timeToLiveInMinutes=1440 | |
28 | +caffeine.specs.entityViews.maxSize=100000 | |
29 | + | |
27 | 30 | caching.specs.devices.timeToLiveInMinutes=1440 |
28 | 31 | caching.specs.devices.maxSize=100000 |
29 | 32 | ... | ... |
... | ... | @@ -18,4 +18,5 @@ DROP TABLE IF EXISTS user_credentials; |
18 | 18 | DROP TABLE IF EXISTS widget_type; |
19 | 19 | DROP TABLE IF EXISTS widgets_bundle; |
20 | 20 | DROP TABLE IF EXISTS rule_node; |
21 | -DROP TABLE IF EXISTS rule_chain; | |
\ No newline at end of file | ||
21 | +DROP TABLE IF EXISTS rule_chain; | |
22 | +DROP TABLE IF EXISTS entity_views; | ... | ... |
... | ... | @@ -30,7 +30,9 @@ spec: |
30 | 30 | value: "cassandra-headless" |
31 | 31 | - name : CASSANDRA_PORT |
32 | 32 | value: "9042" |
33 | - - name : DATABASE_TYPE | |
33 | + - name : DATABASE_ENTITIES_TYPE | |
34 | + value: "cassandra" | |
35 | + - name : DATABASE_TS_TYPE | |
34 | 36 | value: "cassandra" |
35 | 37 | - name : CASSANDRA_URL |
36 | 38 | value: "cassandra-headless:9042" | ... | ... |
... | ... | @@ -30,7 +30,9 @@ spec: |
30 | 30 | value: "cassandra-headless" |
31 | 31 | - name : CASSANDRA_PORT |
32 | 32 | value: "9042" |
33 | - - name : DATABASE_TYPE | |
33 | + - name : DATABASE_ENTITIES_TYPE | |
34 | + value: "cassandra" | |
35 | + - name : DATABASE_TS_TYPE | |
34 | 36 | value: "cassandra" |
35 | 37 | - name : CASSANDRA_URL |
36 | 38 | value: "cassandra-headless:9042" | ... | ... |
... | ... | @@ -120,7 +120,12 @@ spec: |
120 | 120 | configMapKeyRef: |
121 | 121 | name: tb-config |
122 | 122 | key: cassandra.url |
123 | - - name: DATABASE_TYPE | |
123 | + - name: DATABASE_ENTITIES_TYPE | |
124 | + valueFrom: | |
125 | + configMapKeyRef: | |
126 | + name: tb-config | |
127 | + key: database.type | |
128 | + - name: DATABASE_TS_TYPE | |
124 | 129 | valueFrom: |
125 | 130 | configMapKeyRef: |
126 | 131 | name: tb-config | ... | ... |
... | ... | @@ -23,7 +23,7 @@ printenv | awk -F "=" '{print "export " $1 "='\''" $2 "'\''"}' >> /usr/share/thi |
23 | 23 | |
24 | 24 | cat /usr/share/thingsboard/conf/thingsboard.conf |
25 | 25 | |
26 | -if [ "$DATABASE_TYPE" == "cassandra" ]; then | |
26 | +if [ "$DATABASE_ENTITIES_TYPE" == "cassandra" ]; then | |
27 | 27 | until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open\|filtered" |
28 | 28 | do |
29 | 29 | echo "Wait for cassandra db to start..." |
... | ... | @@ -31,7 +31,7 @@ if [ "$DATABASE_TYPE" == "cassandra" ]; then |
31 | 31 | done |
32 | 32 | fi |
33 | 33 | |
34 | -if [ "$DATABASE_TYPE" == "sql" ]; then | |
34 | +if [ "$DATABASE_ENTITIES_TYPE" == "sql" ]; then | |
35 | 35 | if [ "$SPRING_DRIVER_CLASS_NAME" == "org.postgresql.Driver" ]; then |
36 | 36 | until nmap $POSTGRES_HOST -p $POSTGRES_PORT | grep "$POSTGRES_PORT/tcp open" |
37 | 37 | do | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.asset.AssetService; |
26 | 26 | import org.thingsboard.server.dao.attributes.AttributesService; |
27 | 27 | import org.thingsboard.server.dao.customer.CustomerService; |
28 | 28 | import org.thingsboard.server.dao.device.DeviceService; |
29 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
29 | 30 | import org.thingsboard.server.dao.relation.RelationService; |
30 | 31 | import org.thingsboard.server.dao.rule.RuleChainService; |
31 | 32 | import org.thingsboard.server.dao.tenant.TenantService; |
... | ... | @@ -83,6 +84,8 @@ public interface TbContext { |
83 | 84 | |
84 | 85 | RelationService getRelationService(); |
85 | 86 | |
87 | + EntityViewService getEntityViewService(); | |
88 | + | |
86 | 89 | ListeningExecutor getJsExecutor(); |
87 | 90 | |
88 | 91 | ListeningExecutor getMailExecutor(); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import com.google.common.util.concurrent.FutureCallback; | |
19 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | +import com.google.gson.JsonParser; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
22 | +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; | |
23 | +import org.thingsboard.rule.engine.api.RuleNode; | |
24 | +import org.thingsboard.rule.engine.api.TbContext; | |
25 | +import org.thingsboard.rule.engine.api.TbNode; | |
26 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
27 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
28 | +import org.thingsboard.rule.engine.api.TbRelationTypes; | |
29 | +import org.thingsboard.rule.engine.api.util.DonAsynchron; | |
30 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | |
31 | +import org.thingsboard.server.common.data.DataConstants; | |
32 | +import org.thingsboard.server.common.data.EntityView; | |
33 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
34 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
35 | +import org.thingsboard.server.common.msg.TbMsg; | |
36 | +import org.thingsboard.server.common.msg.session.SessionMsgType; | |
37 | +import org.thingsboard.server.common.transport.adaptor.JsonConverter; | |
38 | + | |
39 | +import javax.annotation.Nullable; | |
40 | +import java.util.List; | |
41 | +import java.util.Set; | |
42 | +import java.util.concurrent.ExecutionException; | |
43 | +import java.util.stream.Collectors; | |
44 | + | |
45 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; | |
46 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | |
47 | + | |
48 | +@Slf4j | |
49 | +@RuleNode( | |
50 | + type = ComponentType.ACTION, | |
51 | + name = "copy attributes", | |
52 | + configClazz = EmptyNodeConfiguration.class, | |
53 | + nodeDescription = "Copy attributes from asset/device to entity view and changes message originator to related entity view", | |
54 | + nodeDetails = "Copy attributes from asset/device to related entity view according to entity view configuration. \n " + | |
55 | + "Copy will be done only for attributes that are between start and end dates and according to attribute keys configuration. \n" + | |
56 | + "Changes message originator to related entity view and produces new messages according to count of updated entity views", | |
57 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
58 | + configDirective = "tbNodeEmptyConfig", | |
59 | + icon = "content_copy" | |
60 | +) | |
61 | +public class TbCopyAttributesToEntityViewNode implements TbNode { | |
62 | + | |
63 | + EmptyNodeConfiguration config; | |
64 | + | |
65 | + @Override | |
66 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
67 | + this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class); | |
68 | + } | |
69 | + | |
70 | + @Override | |
71 | + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { | |
72 | + if (!msg.getMetaData().getData().isEmpty()) { | |
73 | + long now = System.currentTimeMillis(); | |
74 | + String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? | |
75 | + DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope"); | |
76 | + | |
77 | + ListenableFuture<List<EntityView>> entityViewsFuture = | |
78 | + ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); | |
79 | + | |
80 | + DonAsynchron.withCallback(entityViewsFuture, | |
81 | + entityViews -> { | |
82 | + for (EntityView entityView : entityViews) { | |
83 | + long startTime = entityView.getStartTimeMs(); | |
84 | + long endTime = entityView.getEndTimeMs(); | |
85 | + if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { | |
86 | + Set<AttributeKvEntry> attributes = | |
87 | + JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes(); | |
88 | + List<AttributeKvEntry> filteredAttributes = | |
89 | + attributes.stream() | |
90 | + .filter(attr -> { | |
91 | + switch (scope) { | |
92 | + case DataConstants.CLIENT_SCOPE: | |
93 | + if (entityView.getKeys().getAttributes().getCs().isEmpty()) { | |
94 | + return true; | |
95 | + } | |
96 | + return entityView.getKeys().getAttributes().getCs().contains(attr.getKey()); | |
97 | + case DataConstants.SERVER_SCOPE: | |
98 | + if (entityView.getKeys().getAttributes().getSs().isEmpty()) { | |
99 | + return true; | |
100 | + } | |
101 | + return entityView.getKeys().getAttributes().getSs().contains(attr.getKey()); | |
102 | + case DataConstants.SHARED_SCOPE: | |
103 | + if (entityView.getKeys().getAttributes().getSh().isEmpty()) { | |
104 | + return true; | |
105 | + } | |
106 | + return entityView.getKeys().getAttributes().getSh().contains(attr.getKey()); | |
107 | + } | |
108 | + return false; | |
109 | + }).collect(Collectors.toList()); | |
110 | + | |
111 | + ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes, | |
112 | + new FutureCallback<Void>() { | |
113 | + @Override | |
114 | + public void onSuccess(@Nullable Void result) { | |
115 | + TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData()); | |
116 | + ctx.tellNext(updMsg, SUCCESS); | |
117 | + } | |
118 | + | |
119 | + @Override | |
120 | + public void onFailure(Throwable t) { | |
121 | + ctx.tellFailure(msg, t); | |
122 | + } | |
123 | + }); | |
124 | + } | |
125 | + } | |
126 | + }, | |
127 | + t -> ctx.tellFailure(msg, t)); | |
128 | + } else { | |
129 | + ctx.tellNext(msg, FAILURE); | |
130 | + } | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public void destroy() { | |
135 | + | |
136 | + } | |
137 | +} | ... | ... |
ui/src/app/api/entity-view.service.js
0 → 100644
1 | +/* | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +import thingsboardTypes from '../common/types.constant'; | |
17 | + | |
18 | +export default angular.module('thingsboard.api.entityView', [thingsboardTypes]) | |
19 | + .factory('entityViewService', EntityViewService) | |
20 | + .name; | |
21 | + | |
22 | +/*@ngInject*/ | |
23 | +function EntityViewService($http, $q, $window, userService, attributeService, customerService, types) { | |
24 | + | |
25 | + var service = { | |
26 | + assignEntityViewToCustomer: assignEntityViewToCustomer, | |
27 | + deleteEntityView: deleteEntityView, | |
28 | + getCustomerEntityViews: getCustomerEntityViews, | |
29 | + getEntityView: getEntityView, | |
30 | + getEntityViews: getEntityViews, | |
31 | + getTenantEntityViews: getTenantEntityViews, | |
32 | + saveEntityView: saveEntityView, | |
33 | + unassignEntityViewFromCustomer: unassignEntityViewFromCustomer, | |
34 | + getEntityViewAttributes: getEntityViewAttributes, | |
35 | + subscribeForEntityViewAttributes: subscribeForEntityViewAttributes, | |
36 | + unsubscribeForEntityViewAttributes: unsubscribeForEntityViewAttributes, | |
37 | + findByQuery: findByQuery, | |
38 | + getEntityViewTypes: getEntityViewTypes | |
39 | + } | |
40 | + | |
41 | + return service; | |
42 | + | |
43 | + function getTenantEntityViews(pageLink, applyCustomersInfo, config, type) { | |
44 | + var deferred = $q.defer(); | |
45 | + var url = '/api/tenant/entityViews?limit=' + pageLink.limit; | |
46 | + if (angular.isDefined(pageLink.textSearch)) { | |
47 | + url += '&textSearch=' + pageLink.textSearch; | |
48 | + } | |
49 | + if (angular.isDefined(pageLink.idOffset)) { | |
50 | + url += '&idOffset=' + pageLink.idOffset; | |
51 | + } | |
52 | + if (angular.isDefined(pageLink.textOffset)) { | |
53 | + url += '&textOffset=' + pageLink.textOffset; | |
54 | + } | |
55 | + if (angular.isDefined(type) && type.length) { | |
56 | + url += '&type=' + type; | |
57 | + } | |
58 | + $http.get(url, config).then(function success(response) { | |
59 | + if (applyCustomersInfo) { | |
60 | + customerService.applyAssignedCustomersInfo(response.data.data).then( | |
61 | + function success(data) { | |
62 | + response.data.data = data; | |
63 | + deferred.resolve(response.data); | |
64 | + }, | |
65 | + function fail() { | |
66 | + deferred.reject(); | |
67 | + } | |
68 | + ); | |
69 | + } else { | |
70 | + deferred.resolve(response.data); | |
71 | + } | |
72 | + }, function fail() { | |
73 | + deferred.reject(); | |
74 | + }); | |
75 | + return deferred.promise; | |
76 | + } | |
77 | + | |
78 | + function getCustomerEntityViews(customerId, pageLink, applyCustomersInfo, config, type) { | |
79 | + var deferred = $q.defer(); | |
80 | + var url = '/api/customer/' + customerId + '/entityViews?limit=' + pageLink.limit; | |
81 | + if (angular.isDefined(pageLink.textSearch)) { | |
82 | + url += '&textSearch=' + pageLink.textSearch; | |
83 | + } | |
84 | + if (angular.isDefined(pageLink.idOffset)) { | |
85 | + url += '&idOffset=' + pageLink.idOffset; | |
86 | + } | |
87 | + if (angular.isDefined(pageLink.textOffset)) { | |
88 | + url += '&textOffset=' + pageLink.textOffset; | |
89 | + } | |
90 | + if (angular.isDefined(type) && type.length) { | |
91 | + url += '&type=' + type; | |
92 | + } | |
93 | + $http.get(url, config).then(function success(response) { | |
94 | + if (applyCustomersInfo) { | |
95 | + customerService.applyAssignedCustomerInfo(response.data.data, customerId).then( | |
96 | + function success(data) { | |
97 | + response.data.data = data; | |
98 | + deferred.resolve(response.data); | |
99 | + }, | |
100 | + function fail() { | |
101 | + deferred.reject(); | |
102 | + } | |
103 | + ); | |
104 | + } else { | |
105 | + deferred.resolve(response.data); | |
106 | + } | |
107 | + }, function fail() { | |
108 | + deferred.reject(); | |
109 | + }); | |
110 | + | |
111 | + return deferred.promise; | |
112 | + } | |
113 | + | |
114 | + function getEntityView(entityViewId, ignoreErrors, config) { | |
115 | + var deferred = $q.defer(); | |
116 | + var url = '/api/entityView/' + entityViewId; | |
117 | + if (!config) { | |
118 | + config = {}; | |
119 | + } | |
120 | + config = Object.assign(config, { ignoreErrors: ignoreErrors }); | |
121 | + $http.get(url, config).then(function success(response) { | |
122 | + deferred.resolve(response.data); | |
123 | + }, function fail(response) { | |
124 | + deferred.reject(response.data); | |
125 | + }); | |
126 | + return deferred.promise; | |
127 | + } | |
128 | + | |
129 | + function getEntityViews(entityViewIds, config) { | |
130 | + var deferred = $q.defer(); | |
131 | + var ids = ''; | |
132 | + for (var i=0;i<entityViewIds.length;i++) { | |
133 | + if (i>0) { | |
134 | + ids += ','; | |
135 | + } | |
136 | + ids += entityViewIds[i]; | |
137 | + } | |
138 | + var url = '/api/entityViews?entityViewIds=' + ids; | |
139 | + $http.get(url, config).then(function success(response) { | |
140 | + var entityViews = response.data; | |
141 | + entityViews.sort(function (entityView1, entityView2) { | |
142 | + var id1 = entityView1.id.id; | |
143 | + var id2 = entityView2.id.id; | |
144 | + var index1 = entityViewIds.indexOf(id1); | |
145 | + var index2 = entityViewIds.indexOf(id2); | |
146 | + return index1 - index2; | |
147 | + }); | |
148 | + deferred.resolve(entityViews); | |
149 | + }, function fail(response) { | |
150 | + deferred.reject(response.data); | |
151 | + }); | |
152 | + return deferred.promise; | |
153 | + } | |
154 | + | |
155 | + function saveEntityView(entityView) { | |
156 | + var deferred = $q.defer(); | |
157 | + var url = '/api/entityView'; | |
158 | + | |
159 | + $http.post(url, entityView).then(function success(response) { | |
160 | + deferred.resolve(response.data); | |
161 | + }, function fail() { | |
162 | + deferred.reject(); | |
163 | + }); | |
164 | + return deferred.promise; | |
165 | + } | |
166 | + | |
167 | + function deleteEntityView(entityViewId) { | |
168 | + var deferred = $q.defer(); | |
169 | + var url = '/api/entityView/' + entityViewId; | |
170 | + $http.delete(url).then(function success() { | |
171 | + deferred.resolve(); | |
172 | + }, function fail() { | |
173 | + deferred.reject(); | |
174 | + }); | |
175 | + return deferred.promise; | |
176 | + } | |
177 | + | |
178 | + function assignEntityViewToCustomer(customerId, entityViewId) { | |
179 | + var deferred = $q.defer(); | |
180 | + var url = '/api/customer/' + customerId + '/entityView/' + entityViewId; | |
181 | + $http.post(url, null).then(function success(response) { | |
182 | + deferred.resolve(response.data); | |
183 | + }, function fail() { | |
184 | + deferred.reject(); | |
185 | + }); | |
186 | + return deferred.promise; | |
187 | + } | |
188 | + | |
189 | + function unassignEntityViewFromCustomer(entityViewId) { | |
190 | + var deferred = $q.defer(); | |
191 | + var url = '/api/customer/entityView/' + entityViewId; | |
192 | + $http.delete(url).then(function success(response) { | |
193 | + deferred.resolve(response.data); | |
194 | + }, function fail() { | |
195 | + deferred.reject(); | |
196 | + }); | |
197 | + return deferred.promise; | |
198 | + } | |
199 | + | |
200 | + function getEntityViewAttributes(entityViewId, attributeScope, query, successCallback, config) { | |
201 | + return attributeService.getEntityAttributes(types.entityType.entityView, entityViewId, attributeScope, query, successCallback, config); | |
202 | + } | |
203 | + | |
204 | + function subscribeForEntityViewAttributes(entityViewId, attributeScope) { | |
205 | + return attributeService.subscribeForEntityAttributes(types.entityType.entityView, entityViewId, attributeScope); | |
206 | + } | |
207 | + | |
208 | + function unsubscribeForEntityViewAttributes(subscriptionId) { | |
209 | + attributeService.unsubscribeForEntityAttributes(subscriptionId); | |
210 | + } | |
211 | + | |
212 | + function findByQuery(query, ignoreErrors, config) { | |
213 | + var deferred = $q.defer(); | |
214 | + var url = '/api/entityViews'; | |
215 | + if (!config) { | |
216 | + config = {}; | |
217 | + } | |
218 | + config = Object.assign(config, { ignoreErrors: ignoreErrors }); | |
219 | + $http.post(url, query, config).then(function success(response) { | |
220 | + deferred.resolve(response.data); | |
221 | + }, function fail() { | |
222 | + deferred.reject(); | |
223 | + }); | |
224 | + return deferred.promise; | |
225 | + } | |
226 | + | |
227 | + function getEntityViewTypes(config) { | |
228 | + var deferred = $q.defer(); | |
229 | + var url = '/api/entityView/types'; | |
230 | + $http.get(url, config).then(function success(response) { | |
231 | + deferred.resolve(response.data); | |
232 | + }, function fail() { | |
233 | + deferred.reject(); | |
234 | + }); | |
235 | + return deferred.promise; | |
236 | + } | |
237 | + | |
238 | +} | ... | ... |
... | ... | @@ -20,8 +20,9 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes]) |
20 | 20 | .name; |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, | |
24 | - assetService, tenantService, customerService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) { | |
23 | +function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, assetService, tenantService, | |
24 | + customerService, ruleChainService, dashboardService, entityRelationService, attributeService, | |
25 | + entityViewService, types, utils) { | |
25 | 26 | var service = { |
26 | 27 | getEntity: getEntity, |
27 | 28 | getEntities: getEntities, |
... | ... | @@ -54,6 +55,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
54 | 55 | case types.entityType.asset: |
55 | 56 | promise = assetService.getAsset(entityId, true, config); |
56 | 57 | break; |
58 | + case types.entityType.entityView: | |
59 | + promise = entityViewService.getEntityView(entityId, true, config); | |
60 | + break; | |
57 | 61 | case types.entityType.tenant: |
58 | 62 | promise = tenantService.getTenant(entityId, config); |
59 | 63 | break; |
... | ... | @@ -239,6 +243,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
239 | 243 | promise = assetService.getTenantAssets(pageLink, false, config, subType); |
240 | 244 | } |
241 | 245 | break; |
246 | + case types.entityType.entityView: | |
247 | + if (user.authority === 'CUSTOMER_USER') { | |
248 | + promise = entityViewService.getCustomerEntityViews(customerId, pageLink, false, config, subType); | |
249 | + } else { | |
250 | + promise = entityViewService.getTenantEntityViews(pageLink, false, config, subType); | |
251 | + } | |
252 | + break; | |
242 | 253 | case types.entityType.tenant: |
243 | 254 | if (user.authority === 'TENANT_ADMIN') { |
244 | 255 | promise = getSingleTenantByPageLinkPromise(pageLink, config); |
... | ... | @@ -725,6 +736,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
725 | 736 | case 'TENANT_ADMIN': |
726 | 737 | entityTypes.device = types.entityType.device; |
727 | 738 | entityTypes.asset = types.entityType.asset; |
739 | + entityTypes.entityView = types.entityType.entityView; | |
728 | 740 | entityTypes.tenant = types.entityType.tenant; |
729 | 741 | entityTypes.customer = types.entityType.customer; |
730 | 742 | entityTypes.dashboard = types.entityType.dashboard; |
... | ... | @@ -735,6 +747,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
735 | 747 | case 'CUSTOMER_USER': |
736 | 748 | entityTypes.device = types.entityType.device; |
737 | 749 | entityTypes.asset = types.entityType.asset; |
750 | + entityTypes.entityView = types.entityType.entityView; | |
738 | 751 | entityTypes.customer = types.entityType.customer; |
739 | 752 | entityTypes.dashboard = types.entityType.dashboard; |
740 | 753 | if (useAliasEntityTypes) { | ... | ... |
... | ... | @@ -67,6 +67,7 @@ import thingsboardClipboard from './services/clipboard.service'; |
67 | 67 | import thingsboardHome from './layout'; |
68 | 68 | import thingsboardApiLogin from './api/login.service'; |
69 | 69 | import thingsboardApiDevice from './api/device.service'; |
70 | +import thingsboardApiEntityView from './api/entity-view.service'; | |
70 | 71 | import thingsboardApiUser from './api/user.service'; |
71 | 72 | import thingsboardApiEntityRelation from './api/entity-relation.service'; |
72 | 73 | import thingsboardApiAsset from './api/asset.service'; |
... | ... | @@ -133,6 +134,7 @@ angular.module('thingsboard', [ |
133 | 134 | thingsboardHome, |
134 | 135 | thingsboardApiLogin, |
135 | 136 | thingsboardApiDevice, |
137 | + thingsboardApiEntityView, | |
136 | 138 | thingsboardApiUser, |
137 | 139 | thingsboardApiEntityRelation, |
138 | 140 | thingsboardApiAsset, | ... | ... |
... | ... | @@ -327,7 +327,8 @@ export default angular.module('thingsboard.types', []) |
327 | 327 | dashboard: "DASHBOARD", |
328 | 328 | alarm: "ALARM", |
329 | 329 | rulechain: "RULE_CHAIN", |
330 | - rulenode: "RULE_NODE" | |
330 | + rulenode: "RULE_NODE", | |
331 | + entityView: "ENTITY_VIEW" | |
331 | 332 | }, |
332 | 333 | aliasEntityType: { |
333 | 334 | current_customer: "CURRENT_CUSTOMER" |
... | ... | @@ -345,6 +346,12 @@ export default angular.module('thingsboard.types', []) |
345 | 346 | list: 'entity.list-of-assets', |
346 | 347 | nameStartsWith: 'entity.asset-name-starts-with' |
347 | 348 | }, |
349 | + "ENTITY_VIEW": { | |
350 | + type: 'entity.type-entity-view', | |
351 | + typePlural: 'entity.type-entity-views', | |
352 | + list: 'entity.list-of-entity-views', | |
353 | + nameStartsWith: 'entity.entity-view-name-starts-with' | |
354 | + }, | |
348 | 355 | "TENANT": { |
349 | 356 | type: 'entity.type-tenant', |
350 | 357 | typePlural: 'entity.type-tenants', | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2018 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog aria-label="{{ 'entity-view.add' | translate }}" tb-help="'entityViews'" help-container-id="help-container"> | |
19 | + <form name="theForm" ng-submit="vm.add()"> | |
20 | + <md-toolbar> | |
21 | + <div class="md-toolbar-tools"> | |
22 | + <h2 translate>entity-view.add</h2> | |
23 | + <span flex></span> | |
24 | + <div id="help-container"></div> | |
25 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | |
26 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | |
27 | + </md-button> | |
28 | + </div> | |
29 | + </md-toolbar> | |
30 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> | |
31 | + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> | |
32 | + <md-dialog-content> | |
33 | + <div class="md-dialog-content"> | |
34 | + <tb-entity-view entity-view="vm.item" is-edit="true" the-form="theForm"></tb-entity-view> | |
35 | + </div> | |
36 | + </md-dialog-content> | |
37 | + <md-dialog-actions layout="row"> | |
38 | + <span flex></span> | |
39 | + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary"> | |
40 | + {{ 'action.add' | translate }} | |
41 | + </md-button> | |
42 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> | |
43 | + </md-dialog-actions> | |
44 | + </form> | |
45 | +</md-dialog> | |
\ No newline at end of file | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +/*@ngInject*/ | |
17 | +export default function AddEntityViewsToCustomerController(entityViewService, $mdDialog, $q, customerId, entityViews) { | |
18 | + | |
19 | + var vm = this; | |
20 | + | |
21 | + vm.entityViews = entityViews; | |
22 | + vm.searchText = ''; | |
23 | + | |
24 | + vm.assign = assign; | |
25 | + vm.cancel = cancel; | |
26 | + vm.hasData = hasData; | |
27 | + vm.noData = noData; | |
28 | + vm.searchEntityViewTextUpdated = searchEntityViewTextUpdated; | |
29 | + vm.toggleEntityViewSelection = toggleEntityViewSelection; | |
30 | + | |
31 | + vm.theEntityViews = { | |
32 | + getItemAtIndex: function (index) { | |
33 | + if (index > vm.entityViews.data.length) { | |
34 | + vm.theEntityViews.fetchMoreItems_(index); | |
35 | + return null; | |
36 | + } | |
37 | + var item = vm.entityViews.data[index]; | |
38 | + if (item) { | |
39 | + item.indexNumber = index + 1; | |
40 | + } | |
41 | + return item; | |
42 | + }, | |
43 | + | |
44 | + getLength: function () { | |
45 | + if (vm.entityViews.hasNext) { | |
46 | + return vm.entityViews.data.length + vm.entityViews.nextPageLink.limit; | |
47 | + } else { | |
48 | + return vm.entityViews.data.length; | |
49 | + } | |
50 | + }, | |
51 | + | |
52 | + fetchMoreItems_: function () { | |
53 | + if (vm.entityViews.hasNext && !vm.entityViews.pending) { | |
54 | + vm.entityViews.pending = true; | |
55 | + entityViewService.getTenantEntityViews(vm.entityViews.nextPageLink, false).then( | |
56 | + function success(entityViews) { | |
57 | + vm.entityViews.data = vm.entityViews.data.concat(entityViews.data); | |
58 | + vm.entityViews.nextPageLink = entityViews.nextPageLink; | |
59 | + vm.entityViews.hasNext = entityViews.hasNext; | |
60 | + if (vm.entityViews.hasNext) { | |
61 | + vm.entityViews.nextPageLink.limit = vm.entityViews.pageSize; | |
62 | + } | |
63 | + vm.entityViews.pending = false; | |
64 | + }, | |
65 | + function fail() { | |
66 | + vm.entityViews.hasNext = false; | |
67 | + vm.entityViews.pending = false; | |
68 | + }); | |
69 | + } | |
70 | + } | |
71 | + }; | |
72 | + | |
73 | + function cancel () { | |
74 | + $mdDialog.cancel(); | |
75 | + } | |
76 | + | |
77 | + function assign() { | |
78 | + var tasks = []; | |
79 | + for (var entityViewId in vm.entityViews.selections) { | |
80 | + tasks.push(entityViewService.assignEntityViewToCustomer(customerId, entityViewId)); | |
81 | + } | |
82 | + $q.all(tasks).then(function () { | |
83 | + $mdDialog.hide(); | |
84 | + }); | |
85 | + } | |
86 | + | |
87 | + function noData() { | |
88 | + return vm.entityViews.data.length == 0 && !vm.entityViews.hasNext; | |
89 | + } | |
90 | + | |
91 | + function hasData() { | |
92 | + return vm.entityViews.data.length > 0; | |
93 | + } | |
94 | + | |
95 | + function toggleEntityViewSelection($event, entityView) { | |
96 | + $event.stopPropagation(); | |
97 | + var selected = angular.isDefined(entityView.selected) && entityView.selected; | |
98 | + entityView.selected = !selected; | |
99 | + if (entityView.selected) { | |
100 | + vm.entityViews.selections[entityView.id.id] = true; | |
101 | + vm.entityViews.selectedCount++; | |
102 | + } else { | |
103 | + delete vm.entityViews.selections[entityView.id.id]; | |
104 | + vm.entityViews.selectedCount--; | |
105 | + } | |
106 | + } | |
107 | + | |
108 | + function searchEntityViewTextUpdated() { | |
109 | + vm.entityViews = { | |
110 | + pageSize: vm.entityViews.pageSize, | |
111 | + data: [], | |
112 | + nextPageLink: { | |
113 | + limit: vm.entityViews.pageSize, | |
114 | + textSearch: vm.searchText | |
115 | + }, | |
116 | + selections: {}, | |
117 | + selectedCount: 0, | |
118 | + hasNext: true, | |
119 | + pending: false | |
120 | + }; | |
121 | + } | |
122 | + | |
123 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2018 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog aria-label="{{ 'entity-view.assign-to-customer' | translate }}"> | |
19 | + <form name="theForm" ng-submit="vm.assign()"> | |
20 | + <md-toolbar> | |
21 | + <div class="md-toolbar-tools"> | |
22 | + <h2 translate>entity-view.assign-entity-view-to-customer</h2> | |
23 | + <span flex></span> | |
24 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | |
25 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | |
26 | + </md-button> | |
27 | + </div> | |
28 | + </md-toolbar> | |
29 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> | |
30 | + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> | |
31 | + <md-dialog-content> | |
32 | + <div class="md-dialog-content"> | |
33 | + <fieldset> | |
34 | + <span translate>entity-view.assign-entity-view-to-customer-text</span> | |
35 | + <md-input-container class="md-block" style='margin-bottom: 0px;'> | |
36 | + <label> </label> | |
37 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons"> | |
38 | + search | |
39 | + </md-icon> | |
40 | + <input id="entity-view-search" autofocus ng-model="vm.searchText" | |
41 | + ng-change="vm.searchEntityViewTextUpdated()" | |
42 | + placeholder="{{ 'common.enter-search' | translate }}"/> | |
43 | + </md-input-container> | |
44 | + <div style='min-height: 150px;'> | |
45 | + <span translate layout-align="center center" | |
46 | + style="text-transform: uppercase; display: flex; height: 150px;" | |
47 | + class="md-subhead" | |
48 | + ng-show="vm.noData()">entity-view.no-entity-views-text</span> | |
49 | + <md-virtual-repeat-container ng-show="vm.hasData()" | |
50 | + tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex | |
51 | + style='min-height: 150px; width: 100%;'> | |
52 | + <md-list> | |
53 | + <md-list-item md-virtual-repeat="entityView in vm.theEntityViews" md-on-demand | |
54 | + class="repeated-item" flex> | |
55 | + <md-checkbox ng-click="vm.toggleEntityViewSelection($event, entityView)" | |
56 | + aria-label="{{ 'item.selected' | translate }}" | |
57 | + ng-checked="entityView.selected"></md-checkbox> | |
58 | + <span> {{ entityView.name }} </span> | |
59 | + </md-list-item> | |
60 | + </md-list> | |
61 | + </md-virtual-repeat-container> | |
62 | + </div> | |
63 | + </fieldset> | |
64 | + </div> | |
65 | + </md-dialog-content> | |
66 | + <md-dialog-actions layout="row"> | |
67 | + <span flex></span> | |
68 | + <md-button ng-disabled="$root.loading || vm.entityViews.selectedCount == 0" type="submit" | |
69 | + class="md-raised md-primary"> | |
70 | + {{ 'action.assign' | translate }} | |
71 | + </md-button> | |
72 | + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | | |
73 | + translate }} | |
74 | + </md-button> | |
75 | + </md-dialog-actions> | |
76 | + </form> | |
77 | +</md-dialog> | |
\ No newline at end of file | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +/*@ngInject*/ | |
17 | +export default function AssignEntityViewToCustomerController(customerService, entityViewService, $mdDialog, $q, entityViewIds, customers) { | |
18 | + | |
19 | + var vm = this; | |
20 | + | |
21 | + vm.customers = customers; | |
22 | + vm.searchText = ''; | |
23 | + | |
24 | + vm.assign = assign; | |
25 | + vm.cancel = cancel; | |
26 | + vm.isCustomerSelected = isCustomerSelected; | |
27 | + vm.hasData = hasData; | |
28 | + vm.noData = noData; | |
29 | + vm.searchCustomerTextUpdated = searchCustomerTextUpdated; | |
30 | + vm.toggleCustomerSelection = toggleCustomerSelection; | |
31 | + | |
32 | + vm.theCustomers = { | |
33 | + getItemAtIndex: function (index) { | |
34 | + if (index > vm.customers.data.length) { | |
35 | + vm.theCustomers.fetchMoreItems_(index); | |
36 | + return null; | |
37 | + } | |
38 | + var item = vm.customers.data[index]; | |
39 | + if (item) { | |
40 | + item.indexNumber = index + 1; | |
41 | + } | |
42 | + return item; | |
43 | + }, | |
44 | + | |
45 | + getLength: function () { | |
46 | + if (vm.customers.hasNext) { | |
47 | + return vm.customers.data.length + vm.customers.nextPageLink.limit; | |
48 | + } else { | |
49 | + return vm.customers.data.length; | |
50 | + } | |
51 | + }, | |
52 | + | |
53 | + fetchMoreItems_: function () { | |
54 | + if (vm.customers.hasNext && !vm.customers.pending) { | |
55 | + vm.customers.pending = true; | |
56 | + customerService.getCustomers(vm.customers.nextPageLink).then( | |
57 | + function success(customers) { | |
58 | + vm.customers.data = vm.customers.data.concat(customers.data); | |
59 | + vm.customers.nextPageLink = customers.nextPageLink; | |
60 | + vm.customers.hasNext = customers.hasNext; | |
61 | + if (vm.customers.hasNext) { | |
62 | + vm.customers.nextPageLink.limit = vm.customers.pageSize; | |
63 | + } | |
64 | + vm.customers.pending = false; | |
65 | + }, | |
66 | + function fail() { | |
67 | + vm.customers.hasNext = false; | |
68 | + vm.customers.pending = false; | |
69 | + }); | |
70 | + } | |
71 | + } | |
72 | + }; | |
73 | + | |
74 | + function cancel() { | |
75 | + $mdDialog.cancel(); | |
76 | + } | |
77 | + | |
78 | + function assign() { | |
79 | + var tasks = []; | |
80 | + for (var i=0; i < entityViewIds.length;i++) { | |
81 | + tasks.push(entityViewService.assignEntityViewToCustomer(vm.customers.selection.id.id, entityViewIds[i])); | |
82 | + } | |
83 | + $q.all(tasks).then(function () { | |
84 | + $mdDialog.hide(); | |
85 | + }); | |
86 | + } | |
87 | + | |
88 | + function noData() { | |
89 | + return vm.customers.data.length == 0 && !vm.customers.hasNext; | |
90 | + } | |
91 | + | |
92 | + function hasData() { | |
93 | + return vm.customers.data.length > 0; | |
94 | + } | |
95 | + | |
96 | + function toggleCustomerSelection($event, customer) { | |
97 | + $event.stopPropagation(); | |
98 | + if (vm.isCustomerSelected(customer)) { | |
99 | + vm.customers.selection = null; | |
100 | + } else { | |
101 | + vm.customers.selection = customer; | |
102 | + } | |
103 | + } | |
104 | + | |
105 | + function isCustomerSelected(customer) { | |
106 | + return vm.customers.selection != null && customer && | |
107 | + customer.id.id === vm.customers.selection.id.id; | |
108 | + } | |
109 | + | |
110 | + function searchCustomerTextUpdated() { | |
111 | + vm.customers = { | |
112 | + pageSize: vm.customers.pageSize, | |
113 | + data: [], | |
114 | + nextPageLink: { | |
115 | + limit: vm.customers.pageSize, | |
116 | + textSearch: vm.searchText | |
117 | + }, | |
118 | + selection: null, | |
119 | + hasNext: true, | |
120 | + pending: false | |
121 | + }; | |
122 | + } | |
123 | +} | ... | ... |