Showing
14 changed files
with
652 additions
and
300 deletions
... | ... | @@ -54,6 +54,7 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
54 | 54 | import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
55 | 55 | import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
56 | 56 | import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; |
57 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; | |
57 | 58 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
58 | 59 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
59 | 60 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; |
... | ... | @@ -92,7 +93,7 @@ import java.util.stream.Collectors; |
92 | 93 | public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService { |
93 | 94 | |
94 | 95 | private static final int DEFAULT_LIMIT = 100; |
95 | - private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); | |
96 | + private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); | |
96 | 97 | |
97 | 98 | @Autowired |
98 | 99 | private TelemetryWebSocketService wsService; |
... | ... | @@ -202,7 +203,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
202 | 203 | //TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached. |
203 | 204 | TbEntityDataSubCtx finalCtx = ctx; |
204 | 205 | ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( |
205 | - () -> refreshDynamicQuery(tenantId, customerId, finalCtx), | |
206 | + () -> refreshDynamicQuery(finalCtx), | |
206 | 207 | dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); |
207 | 208 | finalCtx.setRefreshTask(task); |
208 | 209 | } |
... | ... | @@ -236,6 +237,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
236 | 237 | } |
237 | 238 | |
238 | 239 | @Override |
240 | + public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) { | |
241 | + TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); | |
242 | + if (ctx == null) { | |
243 | + ctx = createSubCtx(session, cmd); | |
244 | + long start = System.currentTimeMillis(); | |
245 | + ctx.fetchData(); | |
246 | + long end = System.currentTimeMillis(); | |
247 | + stats.getRegularQueryInvocationCnt().incrementAndGet(); | |
248 | + stats.getRegularQueryTimeSpent().addAndGet(end - start); | |
249 | + TbEntityCountSubCtx finalCtx = ctx; | |
250 | + ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( | |
251 | + () -> refreshDynamicQuery(finalCtx), | |
252 | + dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); | |
253 | + finalCtx.setRefreshTask(task); | |
254 | + } else { | |
255 | + log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd); | |
256 | + } | |
257 | + } | |
258 | + | |
259 | + @Override | |
239 | 260 | public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) { |
240 | 261 | TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); |
241 | 262 | if (ctx == null) { |
... | ... | @@ -267,7 +288,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
267 | 288 | } |
268 | 289 | } |
269 | 290 | |
270 | - private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) { | |
291 | + private void refreshDynamicQuery(TbAbstractSubCtx finalCtx) { | |
271 | 292 | try { |
272 | 293 | long start = System.currentTimeMillis(); |
273 | 294 | finalCtx.update(); |
... | ... | @@ -299,7 +320,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
299 | 320 | } |
300 | 321 | |
301 | 322 | private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { |
302 | - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); | |
323 | + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); | |
303 | 324 | TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, |
304 | 325 | attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); |
305 | 326 | if (cmd.getQuery() != null) { |
... | ... | @@ -309,8 +330,20 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
309 | 330 | return ctx; |
310 | 331 | } |
311 | 332 | |
333 | + private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { | |
334 | + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); | |
335 | + TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService, | |
336 | + attributesService, stats, sessionRef, cmd.getCmdId()); | |
337 | + if (cmd.getQuery() != null) { | |
338 | + ctx.setAndResolveQuery(cmd.getQuery()); | |
339 | + } | |
340 | + sessionSubs.put(cmd.getCmdId(), ctx); | |
341 | + return ctx; | |
342 | + } | |
343 | + | |
344 | + | |
312 | 345 | private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { |
313 | - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); | |
346 | + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); | |
314 | 347 | TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, |
315 | 348 | attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); |
316 | 349 | ctx.setAndResolveQuery(cmd.getQuery()); |
... | ... | @@ -319,8 +352,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
319 | 352 | } |
320 | 353 | |
321 | 354 | @SuppressWarnings("unchecked") |
322 | - private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) { | |
323 | - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId); | |
355 | + private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) { | |
356 | + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId); | |
324 | 357 | if (sessionSubs != null) { |
325 | 358 | return (T) sessionSubs.get(cmdId); |
326 | 359 | } else { |
... | ... | @@ -464,17 +497,16 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
464 | 497 | cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId())); |
465 | 498 | } |
466 | 499 | |
467 | - private void cleanupAndCancel(TbAbstractDataSubCtx ctx) { | |
500 | + private void cleanupAndCancel(TbAbstractSubCtx ctx) { | |
468 | 501 | if (ctx != null) { |
469 | 502 | ctx.cancelTasks(); |
470 | - ctx.clearEntitySubscriptions(); | |
471 | - ctx.clearDynamicValueSubscriptions(); | |
503 | + ctx.clearSubscriptions(); | |
472 | 504 | } |
473 | 505 | } |
474 | 506 | |
475 | 507 | @Override |
476 | 508 | public void cancelAllSessionSubscriptions(String sessionId) { |
477 | - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId); | |
509 | + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId); | |
478 | 510 | if (sessionSubs != null) { |
479 | 511 | sessionSubs.values().forEach(this::cleanupAndCancel); |
480 | 512 | } | ... | ... |
... | ... | @@ -15,32 +15,16 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.subscription; |
17 | 17 | |
18 | -import com.google.common.util.concurrent.Futures; | |
19 | -import com.google.common.util.concurrent.ListenableFuture; | |
20 | -import com.google.common.util.concurrent.MoreExecutors; | |
21 | -import lombok.Data; | |
22 | 18 | import lombok.Getter; |
23 | -import lombok.Setter; | |
24 | 19 | import lombok.extern.slf4j.Slf4j; |
25 | -import org.thingsboard.server.common.data.id.CustomerId; | |
26 | 20 | import org.thingsboard.server.common.data.id.EntityId; |
27 | -import org.thingsboard.server.common.data.id.TenantId; | |
28 | -import org.thingsboard.server.common.data.id.UserId; | |
29 | -import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
30 | 21 | import org.thingsboard.server.common.data.page.PageData; |
31 | 22 | import org.thingsboard.server.common.data.query.AbstractDataQuery; |
32 | -import org.thingsboard.server.common.data.query.ComplexFilterPredicate; | |
33 | -import org.thingsboard.server.common.data.query.DynamicValue; | |
34 | -import org.thingsboard.server.common.data.query.DynamicValueSourceType; | |
35 | 23 | import org.thingsboard.server.common.data.query.EntityData; |
36 | 24 | import org.thingsboard.server.common.data.query.EntityDataPageLink; |
37 | 25 | import org.thingsboard.server.common.data.query.EntityDataQuery; |
38 | 26 | import org.thingsboard.server.common.data.query.EntityKey; |
39 | 27 | import org.thingsboard.server.common.data.query.EntityKeyType; |
40 | -import org.thingsboard.server.common.data.query.FilterPredicateType; | |
41 | -import org.thingsboard.server.common.data.query.KeyFilter; | |
42 | -import org.thingsboard.server.common.data.query.KeyFilterPredicate; | |
43 | -import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; | |
44 | 28 | import org.thingsboard.server.common.data.query.TsValue; |
45 | 29 | import org.thingsboard.server.dao.attributes.AttributesService; |
46 | 30 | import org.thingsboard.server.dao.entity.EntityService; |
... | ... | @@ -52,140 +36,25 @@ import java.util.ArrayList; |
52 | 36 | import java.util.Arrays; |
53 | 37 | import java.util.Collections; |
54 | 38 | import java.util.HashMap; |
55 | -import java.util.HashSet; | |
56 | 39 | import java.util.List; |
57 | 40 | import java.util.Map; |
58 | -import java.util.Optional; | |
59 | -import java.util.Set; | |
60 | 41 | import java.util.concurrent.ConcurrentHashMap; |
61 | -import java.util.concurrent.ExecutionException; | |
62 | -import java.util.concurrent.ScheduledFuture; | |
63 | 42 | import java.util.function.Function; |
64 | 43 | import java.util.stream.Collectors; |
65 | 44 | |
66 | 45 | @Slf4j |
67 | -@Data | |
68 | -public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> { | |
46 | +public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> extends TbAbstractSubCtx<T> { | |
69 | 47 | |
70 | - protected final String serviceId; | |
71 | - protected final SubscriptionServiceStatistics stats; | |
72 | - protected final TelemetryWebSocketService wsService; | |
73 | - protected final EntityService entityService; | |
74 | - protected final TbLocalSubscriptionService localSubscriptionService; | |
75 | - protected final AttributesService attributesService; | |
76 | - protected final TelemetryWebSocketSessionRef sessionRef; | |
77 | - protected final int cmdId; | |
78 | 48 | protected final Map<Integer, EntityId> subToEntityIdMap; |
79 | - protected final Set<Integer> subToDynamicValueKeySet; | |
80 | - @Getter | |
81 | - protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues; | |
82 | 49 | @Getter |
83 | 50 | protected PageData<EntityData> data; |
84 | - @Getter | |
85 | - @Setter | |
86 | - protected T query; | |
87 | - @Setter | |
88 | - protected volatile ScheduledFuture<?> refreshTask; | |
89 | 51 | |
90 | 52 | public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, |
91 | 53 | EntityService entityService, TbLocalSubscriptionService localSubscriptionService, |
92 | 54 | AttributesService attributesService, SubscriptionServiceStatistics stats, |
93 | 55 | TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
94 | - this.serviceId = serviceId; | |
95 | - this.wsService = wsService; | |
96 | - this.entityService = entityService; | |
97 | - this.localSubscriptionService = localSubscriptionService; | |
98 | - this.attributesService = attributesService; | |
99 | - this.stats = stats; | |
100 | - this.sessionRef = sessionRef; | |
101 | - this.cmdId = cmdId; | |
56 | + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); | |
102 | 57 | this.subToEntityIdMap = new ConcurrentHashMap<>(); |
103 | - this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet(); | |
104 | - this.dynamicValues = new ConcurrentHashMap<>(); | |
105 | - } | |
106 | - | |
107 | - public void setAndResolveQuery(T query) { | |
108 | - dynamicValues.clear(); | |
109 | - this.query = query; | |
110 | - if (query != null && query.getKeyFilters() != null) { | |
111 | - for (KeyFilter filter : query.getKeyFilters()) { | |
112 | - registerDynamicValues(filter.getPredicate()); | |
113 | - } | |
114 | - } | |
115 | - resolve(getTenantId(), getCustomerId(), getUserId()); | |
116 | - } | |
117 | - | |
118 | - public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) { | |
119 | - List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>(); | |
120 | - for (DynamicValueKey key : dynamicValues.keySet()) { | |
121 | - switch (key.getSourceType()) { | |
122 | - case CURRENT_TENANT: | |
123 | - futures.add(resolveEntityValue(tenantId, tenantId, key)); | |
124 | - break; | |
125 | - case CURRENT_CUSTOMER: | |
126 | - if (customerId != null && !customerId.isNullUid()) { | |
127 | - futures.add(resolveEntityValue(tenantId, customerId, key)); | |
128 | - } | |
129 | - break; | |
130 | - case CURRENT_USER: | |
131 | - if (userId != null && !userId.isNullUid()) { | |
132 | - futures.add(resolveEntityValue(tenantId, userId, key)); | |
133 | - } | |
134 | - break; | |
135 | - } | |
136 | - } | |
137 | - try { | |
138 | - Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>(); | |
139 | - for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) { | |
140 | - tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub); | |
141 | - } | |
142 | - for (EntityId entityId : tmpSubMap.keySet()) { | |
143 | - Map<String, Long> keyStates = new HashMap<>(); | |
144 | - Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId); | |
145 | - dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs())); | |
146 | - int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); | |
147 | - TbAttributeSubscription sub = TbAttributeSubscription.builder() | |
148 | - .serviceId(serviceId) | |
149 | - .sessionId(sessionRef.getSessionId()) | |
150 | - .subscriptionId(subIdx) | |
151 | - .tenantId(sessionRef.getSecurityCtx().getTenantId()) | |
152 | - .entityId(entityId) | |
153 | - .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) | |
154 | - .allKeys(false) | |
155 | - .keyStates(keyStates) | |
156 | - .scope(TbAttributeSubscriptionScope.SERVER_SCOPE) | |
157 | - .build(); | |
158 | - subToDynamicValueKeySet.add(subIdx); | |
159 | - localSubscriptionService.addSubscription(sub); | |
160 | - } | |
161 | - } catch (InterruptedException | ExecutionException e) { | |
162 | - log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet()); | |
163 | - } | |
164 | - | |
165 | - } | |
166 | - | |
167 | - private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, | |
168 | - Map<String, DynamicValueKeySub> dynamicValueKeySubMap) { | |
169 | - Map<String, TsValue> latestUpdate = new HashMap<>(); | |
170 | - subscriptionUpdate.getData().forEach((k, v) -> { | |
171 | - Object[] data = (Object[]) v.get(0); | |
172 | - latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); | |
173 | - }); | |
174 | - | |
175 | - boolean invalidateFilter = false; | |
176 | - for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) { | |
177 | - String k = entry.getKey(); | |
178 | - TsValue tsValue = entry.getValue(); | |
179 | - DynamicValueKeySub sub = dynamicValueKeySubMap.get(k); | |
180 | - if (sub.updateValue(tsValue)) { | |
181 | - invalidateFilter = true; | |
182 | - updateDynamicValuesByKey(sub, tsValue); | |
183 | - } | |
184 | - } | |
185 | - | |
186 | - if (invalidateFilter) { | |
187 | - update(); | |
188 | - } | |
189 | 58 | } |
190 | 59 | |
191 | 60 | public void fetchData() { |
... | ... | @@ -231,104 +100,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends |
231 | 100 | return data.getData(); |
232 | 101 | } |
233 | 102 | |
234 | - @Data | |
235 | - private static class DynamicValueKeySub { | |
236 | - private final DynamicValueKey key; | |
237 | - private final EntityId entityId; | |
238 | - private long lastUpdateTs; | |
239 | - private String lastUpdateValue; | |
240 | - | |
241 | - boolean updateValue(TsValue value) { | |
242 | - if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) { | |
243 | - this.lastUpdateTs = value.getTs(); | |
244 | - this.lastUpdateValue = value.getValue(); | |
245 | - return true; | |
246 | - } else { | |
247 | - return false; | |
248 | - } | |
249 | - } | |
250 | - } | |
251 | - | |
252 | - private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) { | |
253 | - ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId, | |
254 | - TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute()); | |
255 | - return Futures.transform(entry, attributeOpt -> { | |
256 | - DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId); | |
257 | - if (attributeOpt.isPresent()) { | |
258 | - AttributeKvEntry attribute = attributeOpt.get(); | |
259 | - sub.setLastUpdateTs(attribute.getLastUpdateTs()); | |
260 | - sub.setLastUpdateValue(attribute.getValueAsString()); | |
261 | - updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString())); | |
262 | - } | |
263 | - return sub; | |
264 | - }, MoreExecutors.directExecutor()); | |
265 | - } | |
266 | - | |
267 | - @SuppressWarnings("unchecked") | |
268 | - private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) { | |
269 | - DynamicValueKey dvk = sub.getKey(); | |
270 | - switch (dvk.getPredicateType()) { | |
271 | - case STRING: | |
272 | - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue())); | |
273 | - break; | |
274 | - case NUMERIC: | |
275 | - try { | |
276 | - Double dValue = Double.parseDouble(tsValue.getValue()); | |
277 | - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue)); | |
278 | - } catch (NumberFormatException e) { | |
279 | - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null)); | |
280 | - } | |
281 | - break; | |
282 | - case BOOLEAN: | |
283 | - Boolean bValue = Boolean.parseBoolean(tsValue.getValue()); | |
284 | - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue)); | |
285 | - break; | |
286 | - } | |
287 | - } | |
288 | - | |
289 | - @SuppressWarnings("unchecked") | |
290 | - private void registerDynamicValues(KeyFilterPredicate predicate) { | |
291 | - switch (predicate.getType()) { | |
292 | - case STRING: | |
293 | - case NUMERIC: | |
294 | - case BOOLEAN: | |
295 | - Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate); | |
296 | - if (value.isPresent()) { | |
297 | - DynamicValue dynamicValue = value.get(); | |
298 | - DynamicValueKey key = new DynamicValueKey( | |
299 | - predicate.getType(), | |
300 | - dynamicValue.getSourceType(), | |
301 | - dynamicValue.getSourceAttribute()); | |
302 | - dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue); | |
303 | - } | |
304 | - break; | |
305 | - case COMPLEX: | |
306 | - ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues); | |
307 | - } | |
308 | - } | |
309 | - | |
310 | - private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) { | |
311 | - if (predicate.getValue().getUserValue() == null) { | |
312 | - return Optional.ofNullable(predicate.getValue().getDynamicValue()); | |
313 | - } else { | |
314 | - return Optional.empty(); | |
315 | - } | |
316 | - } | |
317 | - | |
318 | - public String getSessionId() { | |
319 | - return sessionRef.getSessionId(); | |
320 | - } | |
321 | - | |
322 | - public TenantId getTenantId() { | |
323 | - return sessionRef.getSecurityCtx().getTenantId(); | |
324 | - } | |
325 | - | |
326 | - public CustomerId getCustomerId() { | |
327 | - return sessionRef.getSecurityCtx().getCustomerId(); | |
328 | - } | |
329 | - | |
330 | - public UserId getUserId() { | |
331 | - return sessionRef.getSecurityCtx().getId(); | |
103 | + @Override | |
104 | + public void clearSubscriptions() { | |
105 | + clearEntitySubscriptions(); | |
106 | + super.clearSubscriptions(); | |
332 | 107 | } |
333 | 108 | |
334 | 109 | public void clearEntitySubscriptions() { |
... | ... | @@ -340,26 +115,6 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends |
340 | 115 | } |
341 | 116 | } |
342 | 117 | |
343 | - public void clearDynamicValueSubscriptions() { | |
344 | - if (subToDynamicValueKeySet != null) { | |
345 | - for (Integer subId : subToDynamicValueKeySet) { | |
346 | - localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); | |
347 | - } | |
348 | - subToDynamicValueKeySet.clear(); | |
349 | - } | |
350 | - } | |
351 | - | |
352 | - public void setRefreshTask(ScheduledFuture<?> task) { | |
353 | - this.refreshTask = task; | |
354 | - } | |
355 | - | |
356 | - public void cancelTasks() { | |
357 | - if (this.refreshTask != null) { | |
358 | - log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId); | |
359 | - this.refreshTask.cancel(true); | |
360 | - } | |
361 | - } | |
362 | - | |
363 | 118 | public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) { |
364 | 119 | Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); |
365 | 120 | for (EntityData entityData : data.getData()) { |
... | ... | @@ -459,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends |
459 | 214 | |
460 | 215 | abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues); |
461 | 216 | |
462 | - @Data | |
463 | - private static class DynamicValueKey { | |
464 | - @Getter | |
465 | - private final FilterPredicateType predicateType; | |
466 | - @Getter | |
467 | - private final DynamicValueSourceType sourceType; | |
468 | - @Getter | |
469 | - private final String sourceAttribute; | |
470 | - } | |
471 | - | |
472 | 217 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 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.subscription; | |
17 | + | |
18 | +import com.google.common.util.concurrent.Futures; | |
19 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | +import com.google.common.util.concurrent.MoreExecutors; | |
21 | +import lombok.Data; | |
22 | +import lombok.Getter; | |
23 | +import lombok.Setter; | |
24 | +import lombok.extern.slf4j.Slf4j; | |
25 | +import org.thingsboard.server.common.data.id.CustomerId; | |
26 | +import org.thingsboard.server.common.data.id.EntityId; | |
27 | +import org.thingsboard.server.common.data.id.TenantId; | |
28 | +import org.thingsboard.server.common.data.id.UserId; | |
29 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
30 | +import org.thingsboard.server.common.data.query.ComplexFilterPredicate; | |
31 | +import org.thingsboard.server.common.data.query.DynamicValue; | |
32 | +import org.thingsboard.server.common.data.query.DynamicValueSourceType; | |
33 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
34 | +import org.thingsboard.server.common.data.query.EntityKeyType; | |
35 | +import org.thingsboard.server.common.data.query.FilterPredicateType; | |
36 | +import org.thingsboard.server.common.data.query.KeyFilter; | |
37 | +import org.thingsboard.server.common.data.query.KeyFilterPredicate; | |
38 | +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; | |
39 | +import org.thingsboard.server.common.data.query.TsValue; | |
40 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
41 | +import org.thingsboard.server.dao.entity.EntityService; | |
42 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; | |
43 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; | |
44 | +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; | |
45 | + | |
46 | +import java.util.ArrayList; | |
47 | +import java.util.HashMap; | |
48 | +import java.util.List; | |
49 | +import java.util.Map; | |
50 | +import java.util.Optional; | |
51 | +import java.util.Set; | |
52 | +import java.util.concurrent.ConcurrentHashMap; | |
53 | +import java.util.concurrent.ExecutionException; | |
54 | +import java.util.concurrent.ScheduledFuture; | |
55 | + | |
56 | +@Slf4j | |
57 | +@Data | |
58 | +public abstract class TbAbstractSubCtx<T extends EntityCountQuery> { | |
59 | + | |
60 | + protected final String serviceId; | |
61 | + protected final SubscriptionServiceStatistics stats; | |
62 | + protected final TelemetryWebSocketService wsService; | |
63 | + protected final EntityService entityService; | |
64 | + protected final TbLocalSubscriptionService localSubscriptionService; | |
65 | + protected final AttributesService attributesService; | |
66 | + protected final TelemetryWebSocketSessionRef sessionRef; | |
67 | + protected final int cmdId; | |
68 | + protected final Set<Integer> subToDynamicValueKeySet; | |
69 | + @Getter | |
70 | + protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues; | |
71 | + @Getter | |
72 | + @Setter | |
73 | + protected T query; | |
74 | + @Setter | |
75 | + protected volatile ScheduledFuture<?> refreshTask; | |
76 | + | |
77 | + public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService, | |
78 | + EntityService entityService, TbLocalSubscriptionService localSubscriptionService, | |
79 | + AttributesService attributesService, SubscriptionServiceStatistics stats, | |
80 | + TelemetryWebSocketSessionRef sessionRef, int cmdId) { | |
81 | + this.serviceId = serviceId; | |
82 | + this.wsService = wsService; | |
83 | + this.entityService = entityService; | |
84 | + this.localSubscriptionService = localSubscriptionService; | |
85 | + this.attributesService = attributesService; | |
86 | + this.stats = stats; | |
87 | + this.sessionRef = sessionRef; | |
88 | + this.cmdId = cmdId; | |
89 | + this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet(); | |
90 | + this.dynamicValues = new ConcurrentHashMap<>(); | |
91 | + } | |
92 | + | |
93 | + public void setAndResolveQuery(T query) { | |
94 | + dynamicValues.clear(); | |
95 | + this.query = query; | |
96 | + if (query != null && query.getKeyFilters() != null) { | |
97 | + for (KeyFilter filter : query.getKeyFilters()) { | |
98 | + registerDynamicValues(filter.getPredicate()); | |
99 | + } | |
100 | + } | |
101 | + resolve(getTenantId(), getCustomerId(), getUserId()); | |
102 | + } | |
103 | + | |
104 | + public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) { | |
105 | + List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>(); | |
106 | + for (DynamicValueKey key : dynamicValues.keySet()) { | |
107 | + switch (key.getSourceType()) { | |
108 | + case CURRENT_TENANT: | |
109 | + futures.add(resolveEntityValue(tenantId, tenantId, key)); | |
110 | + break; | |
111 | + case CURRENT_CUSTOMER: | |
112 | + if (customerId != null && !customerId.isNullUid()) { | |
113 | + futures.add(resolveEntityValue(tenantId, customerId, key)); | |
114 | + } | |
115 | + break; | |
116 | + case CURRENT_USER: | |
117 | + if (userId != null && !userId.isNullUid()) { | |
118 | + futures.add(resolveEntityValue(tenantId, userId, key)); | |
119 | + } | |
120 | + break; | |
121 | + } | |
122 | + } | |
123 | + try { | |
124 | + Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>(); | |
125 | + for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) { | |
126 | + tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub); | |
127 | + } | |
128 | + for (EntityId entityId : tmpSubMap.keySet()) { | |
129 | + Map<String, Long> keyStates = new HashMap<>(); | |
130 | + Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId); | |
131 | + dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs())); | |
132 | + int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); | |
133 | + TbAttributeSubscription sub = TbAttributeSubscription.builder() | |
134 | + .serviceId(serviceId) | |
135 | + .sessionId(sessionRef.getSessionId()) | |
136 | + .subscriptionId(subIdx) | |
137 | + .tenantId(sessionRef.getSecurityCtx().getTenantId()) | |
138 | + .entityId(entityId) | |
139 | + .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) | |
140 | + .allKeys(false) | |
141 | + .keyStates(keyStates) | |
142 | + .scope(TbAttributeSubscriptionScope.SERVER_SCOPE) | |
143 | + .build(); | |
144 | + subToDynamicValueKeySet.add(subIdx); | |
145 | + localSubscriptionService.addSubscription(sub); | |
146 | + } | |
147 | + } catch (InterruptedException | ExecutionException e) { | |
148 | + log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet()); | |
149 | + } | |
150 | + | |
151 | + } | |
152 | + | |
153 | + private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, | |
154 | + Map<String, DynamicValueKeySub> dynamicValueKeySubMap) { | |
155 | + Map<String, TsValue> latestUpdate = new HashMap<>(); | |
156 | + subscriptionUpdate.getData().forEach((k, v) -> { | |
157 | + Object[] data = (Object[]) v.get(0); | |
158 | + latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); | |
159 | + }); | |
160 | + | |
161 | + boolean invalidateFilter = false; | |
162 | + for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) { | |
163 | + String k = entry.getKey(); | |
164 | + TsValue tsValue = entry.getValue(); | |
165 | + DynamicValueKeySub sub = dynamicValueKeySubMap.get(k); | |
166 | + if (sub.updateValue(tsValue)) { | |
167 | + invalidateFilter = true; | |
168 | + updateDynamicValuesByKey(sub, tsValue); | |
169 | + } | |
170 | + } | |
171 | + | |
172 | + if (invalidateFilter) { | |
173 | + update(); | |
174 | + } | |
175 | + } | |
176 | + | |
177 | + public abstract void fetchData(); | |
178 | + | |
179 | + protected abstract void update(); | |
180 | + | |
181 | + public void clearSubscriptions() { | |
182 | + clearDynamicValueSubscriptions(); | |
183 | + } | |
184 | + | |
185 | + @Data | |
186 | + private static class DynamicValueKeySub { | |
187 | + private final DynamicValueKey key; | |
188 | + private final EntityId entityId; | |
189 | + private long lastUpdateTs; | |
190 | + private String lastUpdateValue; | |
191 | + | |
192 | + boolean updateValue(TsValue value) { | |
193 | + if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) { | |
194 | + this.lastUpdateTs = value.getTs(); | |
195 | + this.lastUpdateValue = value.getValue(); | |
196 | + return true; | |
197 | + } else { | |
198 | + return false; | |
199 | + } | |
200 | + } | |
201 | + } | |
202 | + | |
203 | + private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) { | |
204 | + ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId, | |
205 | + TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute()); | |
206 | + return Futures.transform(entry, attributeOpt -> { | |
207 | + DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId); | |
208 | + if (attributeOpt.isPresent()) { | |
209 | + AttributeKvEntry attribute = attributeOpt.get(); | |
210 | + sub.setLastUpdateTs(attribute.getLastUpdateTs()); | |
211 | + sub.setLastUpdateValue(attribute.getValueAsString()); | |
212 | + updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString())); | |
213 | + } | |
214 | + return sub; | |
215 | + }, MoreExecutors.directExecutor()); | |
216 | + } | |
217 | + | |
218 | + @SuppressWarnings("unchecked") | |
219 | + protected void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) { | |
220 | + DynamicValueKey dvk = sub.getKey(); | |
221 | + switch (dvk.getPredicateType()) { | |
222 | + case STRING: | |
223 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue())); | |
224 | + break; | |
225 | + case NUMERIC: | |
226 | + try { | |
227 | + Double dValue = Double.parseDouble(tsValue.getValue()); | |
228 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue)); | |
229 | + } catch (NumberFormatException e) { | |
230 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null)); | |
231 | + } | |
232 | + break; | |
233 | + case BOOLEAN: | |
234 | + Boolean bValue = Boolean.parseBoolean(tsValue.getValue()); | |
235 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue)); | |
236 | + break; | |
237 | + } | |
238 | + } | |
239 | + | |
240 | + @SuppressWarnings("unchecked") | |
241 | + private void registerDynamicValues(KeyFilterPredicate predicate) { | |
242 | + switch (predicate.getType()) { | |
243 | + case STRING: | |
244 | + case NUMERIC: | |
245 | + case BOOLEAN: | |
246 | + Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate); | |
247 | + if (value.isPresent()) { | |
248 | + DynamicValue dynamicValue = value.get(); | |
249 | + DynamicValueKey key = new DynamicValueKey( | |
250 | + predicate.getType(), | |
251 | + dynamicValue.getSourceType(), | |
252 | + dynamicValue.getSourceAttribute()); | |
253 | + dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue); | |
254 | + } | |
255 | + break; | |
256 | + case COMPLEX: | |
257 | + ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues); | |
258 | + } | |
259 | + } | |
260 | + | |
261 | + private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) { | |
262 | + if (predicate.getValue().getUserValue() == null) { | |
263 | + return Optional.ofNullable(predicate.getValue().getDynamicValue()); | |
264 | + } else { | |
265 | + return Optional.empty(); | |
266 | + } | |
267 | + } | |
268 | + | |
269 | + public String getSessionId() { | |
270 | + return sessionRef.getSessionId(); | |
271 | + } | |
272 | + | |
273 | + public TenantId getTenantId() { | |
274 | + return sessionRef.getSecurityCtx().getTenantId(); | |
275 | + } | |
276 | + | |
277 | + public CustomerId getCustomerId() { | |
278 | + return sessionRef.getSecurityCtx().getCustomerId(); | |
279 | + } | |
280 | + | |
281 | + public UserId getUserId() { | |
282 | + return sessionRef.getSecurityCtx().getId(); | |
283 | + } | |
284 | + | |
285 | + protected void clearDynamicValueSubscriptions() { | |
286 | + if (subToDynamicValueKeySet != null) { | |
287 | + for (Integer subId : subToDynamicValueKeySet) { | |
288 | + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); | |
289 | + } | |
290 | + subToDynamicValueKeySet.clear(); | |
291 | + } | |
292 | + } | |
293 | + | |
294 | + public void setRefreshTask(ScheduledFuture<?> task) { | |
295 | + this.refreshTask = task; | |
296 | + } | |
297 | + | |
298 | + public void cancelTasks() { | |
299 | + if (this.refreshTask != null) { | |
300 | + log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId); | |
301 | + this.refreshTask.cancel(true); | |
302 | + } | |
303 | + } | |
304 | + | |
305 | + @Data | |
306 | + public static class DynamicValueKey { | |
307 | + @Getter | |
308 | + private final FilterPredicateType predicateType; | |
309 | + @Getter | |
310 | + private final DynamicValueSourceType sourceType; | |
311 | + @Getter | |
312 | + private final String sourceAttribute; | |
313 | + } | |
314 | + | |
315 | +} | ... | ... |
... | ... | @@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> { |
90 | 90 | AlarmDataUpdate update; |
91 | 91 | if (!entitiesMap.isEmpty()) { |
92 | 92 | long start = System.currentTimeMillis(); |
93 | - PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), | |
94 | - query, getOrderedEntityIds()); | |
93 | + PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds()); | |
95 | 94 | long end = System.currentTimeMillis(); |
96 | 95 | stats.getAlarmQueryInvocationCnt().incrementAndGet(); |
97 | 96 | stats.getAlarmQueryTimeSpent().addAndGet(end - start); | ... | ... |
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 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.subscription; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
20 | +import org.thingsboard.server.common.data.query.EntityKeyType; | |
21 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
22 | +import org.thingsboard.server.dao.entity.EntityService; | |
23 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; | |
24 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; | |
25 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; | |
26 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; | |
27 | +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; | |
28 | + | |
29 | +@Slf4j | |
30 | +public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> { | |
31 | + | |
32 | + private volatile int result; | |
33 | + | |
34 | + public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, | |
35 | + TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, | |
36 | + SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) { | |
37 | + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); | |
38 | + } | |
39 | + | |
40 | + @Override | |
41 | + public void fetchData() { | |
42 | + result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query); | |
43 | + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result)); | |
44 | + } | |
45 | + | |
46 | + @Override | |
47 | + protected void update() { | |
48 | + int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query); | |
49 | + if (newCount != result) { | |
50 | + result = newCount; | |
51 | + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result)); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | +} | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription; |
17 | 17 | |
18 | 18 | import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
19 | 19 | import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
20 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; | |
20 | 21 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
21 | 22 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; |
22 | 23 | import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; |
... | ... | @@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService { |
25 | 26 | |
26 | 27 | void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd); |
27 | 28 | |
29 | + void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd); | |
30 | + | |
28 | 31 | void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd); |
29 | 32 | |
30 | 33 | void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); | ... | ... |
... | ... | @@ -51,22 +51,22 @@ import org.thingsboard.server.service.security.ValidationResult; |
51 | 51 | import org.thingsboard.server.service.security.ValidationResultCode; |
52 | 52 | import org.thingsboard.server.service.security.model.UserPrincipal; |
53 | 53 | import org.thingsboard.server.service.security.permission.Operation; |
54 | +import org.thingsboard.server.service.subscription.TbAttributeSubscription; | |
55 | +import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; | |
54 | 56 | import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService; |
55 | 57 | import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; |
56 | -import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; | |
57 | -import org.thingsboard.server.service.subscription.TbAttributeSubscription; | |
58 | 58 | import org.thingsboard.server.service.subscription.TbTimeseriesSubscription; |
59 | +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; | |
59 | 60 | import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; |
60 | 61 | import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; |
61 | 62 | import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd; |
62 | 63 | import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd; |
63 | -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; | |
64 | 64 | import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; |
65 | 65 | import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
66 | -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; | |
66 | +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; | |
67 | 67 | import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate; |
68 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; | |
68 | 69 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
69 | -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; | |
70 | 70 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
71 | 71 | import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; |
72 | 72 | import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; |
... | ... | @@ -216,12 +216,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
216 | 216 | if (cmdsWrapper.getAlarmDataCmds() != null) { |
217 | 217 | cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd)); |
218 | 218 | } |
219 | + if (cmdsWrapper.getEntityCountCmds() != null) { | |
220 | + cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd)); | |
221 | + } | |
219 | 222 | if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) { |
220 | 223 | cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); |
221 | 224 | } |
222 | 225 | if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) { |
223 | 226 | cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); |
224 | 227 | } |
228 | + if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) { | |
229 | + cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); | |
230 | + } | |
225 | 231 | } |
226 | 232 | } catch (IOException e) { |
227 | 233 | log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); |
... | ... | @@ -239,6 +245,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
239 | 245 | } |
240 | 246 | } |
241 | 247 | |
248 | + private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { | |
249 | + String sessionId = sessionRef.getSessionId(); | |
250 | + log.debug("[{}] Processing: {}", sessionId, cmd); | |
251 | + | |
252 | + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId) | |
253 | + && validateSubscriptionCmd(sessionRef, cmd)) { | |
254 | + entityDataSubService.handleCmd(sessionRef, cmd); | |
255 | + } | |
256 | + } | |
257 | + | |
242 | 258 | private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { |
243 | 259 | String sessionId = sessionRef.getSessionId(); |
244 | 260 | log.debug("[{}] Processing: {}", sessionId, cmd); |
... | ... | @@ -264,7 +280,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
264 | 280 | } |
265 | 281 | |
266 | 282 | @Override |
267 | - public void sendWsMsg(String sessionId, DataUpdate update) { | |
283 | + public void sendWsMsg(String sessionId, CmdUpdate update) { | |
268 | 284 | sendWsMsg(sessionId, update.getCmdId(), update); |
269 | 285 | } |
270 | 286 | |
... | ... | @@ -679,6 +695,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
679 | 695 | return true; |
680 | 696 | } |
681 | 697 | |
698 | + private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) { | |
699 | + if (cmd.getCmdId() < 0) { | |
700 | + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, | |
701 | + "Cmd id is negative value!"); | |
702 | + sendWsMsg(sessionRef, update); | |
703 | + return false; | |
704 | + } else if (cmd.getQuery() == null) { | |
705 | + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!"); | |
706 | + sendWsMsg(sessionRef, update); | |
707 | + return false; | |
708 | + } | |
709 | + return true; | |
710 | + } | |
711 | + | |
682 | 712 | private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { |
683 | 713 | if (cmd.getCmdId() < 0) { |
684 | 714 | TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, | ... | ... |
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.telemetry; |
17 | 17 | |
18 | +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate; | |
18 | 19 | import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate; |
19 | 20 | import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; |
20 | 21 | |
... | ... | @@ -29,6 +30,6 @@ public interface TelemetryWebSocketService { |
29 | 30 | |
30 | 31 | void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update); |
31 | 32 | |
32 | - void sendWsMsg(String sessionId, DataUpdate update); | |
33 | + void sendWsMsg(String sessionId, CmdUpdate update); | |
33 | 34 | |
34 | 35 | } | ... | ... |
... | ... | @@ -15,11 +15,13 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.telemetry.cmd.v2; |
17 | 17 | |
18 | +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |
18 | 19 | import lombok.AllArgsConstructor; |
19 | 20 | import lombok.Data; |
20 | 21 | |
21 | 22 | @Data |
22 | 23 | @AllArgsConstructor |
24 | +@JsonIgnoreProperties(ignoreUnknown = true) | |
23 | 25 | public abstract class CmdUpdate { |
24 | 26 | |
25 | 27 | private final int cmdId; | ... | ... |
... | ... | @@ -15,18 +15,12 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.telemetry.cmd.v2; |
17 | 17 | |
18 | -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |
19 | -import lombok.AllArgsConstructor; | |
20 | -import lombok.Data; | |
21 | -import lombok.EqualsAndHashCode; | |
22 | 18 | import lombok.Getter; |
23 | -import lombok.ToString; | |
24 | 19 | import org.thingsboard.server.common.data.page.PageData; |
25 | 20 | import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
26 | 21 | |
27 | 22 | import java.util.List; |
28 | 23 | |
29 | -@JsonIgnoreProperties(ignoreUnknown = true) | |
30 | 24 | public abstract class DataUpdate<T> extends CmdUpdate { |
31 | 25 | |
32 | 26 | @Getter | ... | ... |
... | ... | @@ -35,16 +35,23 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; |
35 | 35 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
36 | 36 | import org.thingsboard.server.common.data.page.PageData; |
37 | 37 | import org.thingsboard.server.common.data.query.DeviceTypeFilter; |
38 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
38 | 39 | import org.thingsboard.server.common.data.query.EntityData; |
39 | 40 | import org.thingsboard.server.common.data.query.EntityDataPageLink; |
40 | 41 | import org.thingsboard.server.common.data.query.EntityDataQuery; |
41 | 42 | import org.thingsboard.server.common.data.query.EntityKey; |
42 | 43 | import org.thingsboard.server.common.data.query.EntityKeyType; |
44 | +import org.thingsboard.server.common.data.query.EntityKeyValueType; | |
45 | +import org.thingsboard.server.common.data.query.FilterPredicateValue; | |
46 | +import org.thingsboard.server.common.data.query.KeyFilter; | |
47 | +import org.thingsboard.server.common.data.query.NumericFilterPredicate; | |
43 | 48 | import org.thingsboard.server.common.data.query.TsValue; |
44 | 49 | import org.thingsboard.server.common.data.security.Authority; |
45 | 50 | import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; |
46 | 51 | import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
47 | 52 | import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; |
53 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; | |
54 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; | |
48 | 55 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
49 | 56 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
50 | 57 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; |
... | ... | @@ -244,6 +251,98 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest { |
244 | 251 | } |
245 | 252 | |
246 | 253 | @Test |
254 | + public void testEntityCountWsCmd() throws Exception { | |
255 | + Device device = new Device(); | |
256 | + device.setName("Device"); | |
257 | + device.setType("default"); | |
258 | + device.setLabel("testLabel" + (int) (Math.random() * 1000)); | |
259 | + device = doPost("/api/device", device, Device.class); | |
260 | + | |
261 | + AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L)); | |
262 | + sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1)); | |
263 | + | |
264 | + DeviceTypeFilter dtf1 = new DeviceTypeFilter(); | |
265 | + dtf1.setDeviceNameFilter("D"); | |
266 | + dtf1.setDeviceType("default"); | |
267 | + EntityCountQuery edq1 = new EntityCountQuery(dtf1, Collections.emptyList()); | |
268 | + | |
269 | + EntityCountCmd cmd1 = new EntityCountCmd(1, edq1); | |
270 | + | |
271 | + TelemetryPluginCmdsWrapper wrapper1 = new TelemetryPluginCmdsWrapper(); | |
272 | + wrapper1.setEntityCountCmds(Collections.singletonList(cmd1)); | |
273 | + | |
274 | + wsClient.send(mapper.writeValueAsString(wrapper1)); | |
275 | + String msg1 = wsClient.waitForReply(); | |
276 | + EntityCountUpdate update1 = mapper.readValue(msg1, EntityCountUpdate.class); | |
277 | + Assert.assertEquals(1, update1.getCmdId()); | |
278 | + Assert.assertEquals(1, update1.getCount()); | |
279 | + | |
280 | + DeviceTypeFilter dtf2 = new DeviceTypeFilter(); | |
281 | + dtf2.setDeviceNameFilter("D"); | |
282 | + dtf2.setDeviceType("non-existing-device-type"); | |
283 | + EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList()); | |
284 | + | |
285 | + EntityCountCmd cmd2 = new EntityCountCmd(2, edq2); | |
286 | + | |
287 | + TelemetryPluginCmdsWrapper wrapper2 = new TelemetryPluginCmdsWrapper(); | |
288 | + wrapper2.setEntityCountCmds(Collections.singletonList(cmd2)); | |
289 | + wsClient.send(mapper.writeValueAsString(wrapper2)); | |
290 | + | |
291 | + String msg2 = wsClient.waitForReply(); | |
292 | + EntityCountUpdate update2 = mapper.readValue(msg2, EntityCountUpdate.class); | |
293 | + Assert.assertEquals(2, update2.getCmdId()); | |
294 | + Assert.assertEquals(0, update2.getCount()); | |
295 | + | |
296 | + KeyFilter highTemperatureFilter = new KeyFilter(); | |
297 | + highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); | |
298 | + NumericFilterPredicate predicate = new NumericFilterPredicate(); | |
299 | + predicate.setValue(FilterPredicateValue.fromDouble(40)); | |
300 | + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); | |
301 | + highTemperatureFilter.setPredicate(predicate); | |
302 | + highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC); | |
303 | + | |
304 | + DeviceTypeFilter dtf3 = new DeviceTypeFilter(); | |
305 | + dtf3.setDeviceNameFilter("D"); | |
306 | + dtf3.setDeviceType("default"); | |
307 | + EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter)); | |
308 | + | |
309 | + EntityCountCmd cmd3 = new EntityCountCmd(3, edq3); | |
310 | + | |
311 | + TelemetryPluginCmdsWrapper wrapper3 = new TelemetryPluginCmdsWrapper(); | |
312 | + wrapper3.setEntityCountCmds(Collections.singletonList(cmd3)); | |
313 | + wsClient.send(mapper.writeValueAsString(wrapper3)); | |
314 | + | |
315 | + String msg3 = wsClient.waitForReply(); | |
316 | + EntityCountUpdate update3 = mapper.readValue(msg3, EntityCountUpdate.class); | |
317 | + Assert.assertEquals(3, update3.getCmdId()); | |
318 | + Assert.assertEquals(1, update3.getCount()); | |
319 | + | |
320 | + KeyFilter highTemperatureFilter2 = new KeyFilter(); | |
321 | + highTemperatureFilter2.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); | |
322 | + NumericFilterPredicate predicate2 = new NumericFilterPredicate(); | |
323 | + predicate2.setValue(FilterPredicateValue.fromDouble(50)); | |
324 | + predicate2.setOperation(NumericFilterPredicate.NumericOperation.GREATER); | |
325 | + highTemperatureFilter2.setPredicate(predicate2); | |
326 | + highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC); | |
327 | + | |
328 | + DeviceTypeFilter dtf4 = new DeviceTypeFilter(); | |
329 | + dtf4.setDeviceNameFilter("D"); | |
330 | + dtf4.setDeviceType("default"); | |
331 | + EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2)); | |
332 | + | |
333 | + EntityCountCmd cmd4 = new EntityCountCmd(4, edq4); | |
334 | + | |
335 | + TelemetryPluginCmdsWrapper wrapper4 = new TelemetryPluginCmdsWrapper(); | |
336 | + wrapper4.setEntityCountCmds(Collections.singletonList(cmd4)); | |
337 | + wsClient.send(mapper.writeValueAsString(wrapper4)); | |
338 | + | |
339 | + String msg4 = wsClient.waitForReply(); | |
340 | + EntityCountUpdate update4 = mapper.readValue(msg4, EntityCountUpdate.class); | |
341 | + Assert.assertEquals(4, update4.getCmdId()); | |
342 | + Assert.assertEquals(0, update4.getCount()); | |
343 | + } | |
344 | + | |
345 | + @Test | |
247 | 346 | public void testEntityDataLatestWidgetFlow() throws Exception { |
248 | 347 | Device device = new Device(); |
249 | 348 | device.setName("Device"); | ... | ... |
... | ... | @@ -26,9 +26,9 @@ import java.util.Arrays; |
26 | 26 | |
27 | 27 | @RunWith(ClasspathSuite.class) |
28 | 28 | @ClasspathSuite.ClassnameFilters({ |
29 | -// "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", | |
29 | + "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", | |
30 | 30 | // "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest", |
31 | - "org.thingsboard.server.controller.sql.*Test", | |
31 | +// "org.thingsboard.server.controller.sql.*Test", | |
32 | 32 | }) |
33 | 33 | public class ControllerSqlTestSuite { |
34 | 34 | ... | ... |
... | ... | @@ -249,18 +249,70 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
249 | 249 | public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) { |
250 | 250 | EntityType entityType = resolveEntityType(query.getEntityFilter()); |
251 | 251 | QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, entityType)); |
252 | - ctx.append("select count(e.id) from "); | |
253 | - ctx.append(addEntityTableQuery(ctx, query.getEntityFilter())); | |
254 | - ctx.append(" e where "); | |
255 | - ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList())); | |
256 | - return transactionTemplate.execute(status -> { | |
257 | - long startTs = System.currentTimeMillis(); | |
258 | - try { | |
259 | - return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class); | |
260 | - } finally { | |
261 | - queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs); | |
252 | + if (query.getKeyFilters() == null || query.getKeyFilters().isEmpty()) { | |
253 | + ctx.append("select count(e.id) from "); | |
254 | + ctx.append(addEntityTableQuery(ctx, query.getEntityFilter())); | |
255 | + ctx.append(" e where "); | |
256 | + ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList())); | |
257 | + return transactionTemplate.execute(status -> { | |
258 | + long startTs = System.currentTimeMillis(); | |
259 | + try { | |
260 | + return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class); | |
261 | + } finally { | |
262 | + queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs); | |
263 | + } | |
264 | + }); | |
265 | + } else { | |
266 | + List<EntityKeyMapping> mappings = EntityKeyMapping.prepareEntityCountKeyMapping(query); | |
267 | + | |
268 | + List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection) | |
269 | + .collect(Collectors.toList()); | |
270 | + List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest()) | |
271 | + .collect(Collectors.toList()); | |
272 | + | |
273 | + List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter) | |
274 | + .collect(Collectors.toList()); | |
275 | + List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest()) | |
276 | + .collect(Collectors.toList()); | |
277 | + | |
278 | + List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest) | |
279 | + .collect(Collectors.toList()); | |
280 | + | |
281 | + | |
282 | + String entityWhereClause = DefaultEntityQueryRepository.this.buildEntityWhere(ctx, query.getEntityFilter(), entityFieldsFiltersMapping); | |
283 | + String latestJoinsCnt = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings, true); | |
284 | + String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType); | |
285 | + String entityTypeStr; | |
286 | + if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) { | |
287 | + entityTypeStr = "e.entity_type"; | |
288 | + } else { | |
289 | + entityTypeStr = "'" + entityType.name() + "'"; | |
262 | 290 | } |
263 | - }); | |
291 | + if (!StringUtils.isEmpty(entityFieldsSelection)) { | |
292 | + entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection); | |
293 | + } else { | |
294 | + entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr); | |
295 | + } | |
296 | + | |
297 | + String fromClauseCount = String.format("from (select %s from (select %s from %s e where %s) entities %s ) result %s", | |
298 | + "entities.*", | |
299 | + entityFieldsSelection, | |
300 | + addEntityTableQuery(ctx, query.getEntityFilter()), | |
301 | + entityWhereClause, | |
302 | + latestJoinsCnt, | |
303 | + ""); | |
304 | + | |
305 | + String countQuery = String.format("select count(id) %s", fromClauseCount); | |
306 | + | |
307 | + return transactionTemplate.execute(status -> { | |
308 | + long startTs = System.currentTimeMillis(); | |
309 | + try { | |
310 | + return jdbcTemplate.queryForObject(countQuery, ctx, Long.class); | |
311 | + } finally { | |
312 | + queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs); | |
313 | + } | |
314 | + }); | |
315 | + } | |
264 | 316 | } |
265 | 317 | |
266 | 318 | @Override | ... | ... |
... | ... | @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DataConstants; |
21 | 21 | import org.thingsboard.server.common.data.EntityType; |
22 | 22 | import org.thingsboard.server.common.data.query.BooleanFilterPredicate; |
23 | 23 | import org.thingsboard.server.common.data.query.ComplexFilterPredicate; |
24 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
24 | 25 | import org.thingsboard.server.common.data.query.EntityDataQuery; |
25 | 26 | import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
26 | 27 | import org.thingsboard.server.common.data.query.EntityFilter; |
... | ... | @@ -380,6 +381,30 @@ public class EntityKeyMapping { |
380 | 381 | return mappings; |
381 | 382 | } |
382 | 383 | |
384 | + public static List<EntityKeyMapping> prepareEntityCountKeyMapping(EntityCountQuery query) { | |
385 | + Map<EntityKey, List<KeyFilter>> filters = | |
386 | + query.getKeyFilters() != null ? | |
387 | + query.getKeyFilters().stream().collect(Collectors.groupingBy(KeyFilter::getKey)) : Collections.emptyMap(); | |
388 | + int index = 2; | |
389 | + List<EntityKeyMapping> mappings = new ArrayList<>(); | |
390 | + if (!filters.isEmpty()) { | |
391 | + for (EntityKey filterField : filters.keySet()) { | |
392 | + EntityKeyMapping mapping = new EntityKeyMapping(); | |
393 | + mapping.setIndex(index); | |
394 | + mapping.setAlias(String.format("alias%s", index)); | |
395 | + mapping.setKeyFilters(filters.get(filterField)); | |
396 | + mapping.setLatest(!filterField.getType().equals(EntityKeyType.ENTITY_FIELD)); | |
397 | + mapping.setSelection(false); | |
398 | + mapping.setEntityKey(filterField); | |
399 | + mappings.add(mapping); | |
400 | + index += 1; | |
401 | + } | |
402 | + } | |
403 | + | |
404 | + return mappings; | |
405 | + } | |
406 | + | |
407 | + | |
383 | 408 | private String buildAttributeSelection() { |
384 | 409 | return buildTimeSeriesOrAttrSelection(true); |
385 | 410 | } | ... | ... |