Showing
10 changed files
with
82 additions
and
149 deletions
... | ... | @@ -151,20 +151,9 @@ public final class PluginProcessingContext implements PluginContext { |
151 | 151 | } |
152 | 152 | |
153 | 153 | @Override |
154 | - public List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query) { | |
154 | + public void loadTimeseries(DeviceId deviceId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback) { | |
155 | 155 | validate(deviceId); |
156 | - try { | |
157 | - return pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, query).get(); | |
158 | - } catch (Exception e) { | |
159 | - log.error("TODO", e); | |
160 | - throw new RuntimeException(e); | |
161 | - } | |
162 | - } | |
163 | - | |
164 | - @Override | |
165 | - public void loadTimeseries(DeviceId deviceId, TsKvQuery query, PluginCallback<List<TsKvEntry>> callback) { | |
166 | - validate(deviceId); | |
167 | - ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, query); | |
156 | + ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, queries); | |
168 | 157 | Futures.addCallback(future, getCallback(callback, v -> v), executor); |
169 | 158 | } |
170 | 159 | ... | ... |
... | ... | @@ -96,7 +96,21 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao { |
96 | 96 | } |
97 | 97 | |
98 | 98 | @Override |
99 | - public ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query) { | |
99 | + public ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries) { | |
100 | + List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityType, entityId, query)).collect(Collectors.toList()); | |
101 | + return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() { | |
102 | + @Nullable | |
103 | + @Override | |
104 | + public List<TsKvEntry> apply(@Nullable List<List<TsKvEntry>> results) { | |
105 | + List<TsKvEntry> result = new ArrayList<TsKvEntry>(); | |
106 | + results.forEach(r -> result.addAll(r)); | |
107 | + return result; | |
108 | + } | |
109 | + }, readResultsProcessingExecutor); | |
110 | + } | |
111 | + | |
112 | + | |
113 | + private ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query) { | |
100 | 114 | if (query.getAggregation() == Aggregation.NONE) { |
101 | 115 | return findAllAsyncWithLimit(entityType, entityId, query); |
102 | 116 | } else { | ... | ... |
... | ... | @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.timeseries; |
18 | 18 | import com.datastax.driver.core.ResultSet; |
19 | 19 | import com.datastax.driver.core.ResultSetFuture; |
20 | 20 | import com.datastax.driver.core.Row; |
21 | +import com.google.common.base.Function; | |
21 | 22 | import com.google.common.collect.Lists; |
22 | 23 | import com.google.common.util.concurrent.Futures; |
23 | 24 | import com.google.common.util.concurrent.ListenableFuture; |
... | ... | @@ -32,6 +33,7 @@ import org.springframework.beans.factory.annotation.Value; |
32 | 33 | import org.springframework.stereotype.Service; |
33 | 34 | import org.thingsboard.server.dao.service.Validator; |
34 | 35 | |
36 | +import javax.annotation.Nullable; | |
35 | 37 | import javax.annotation.PostConstruct; |
36 | 38 | import javax.annotation.PreDestroy; |
37 | 39 | import java.time.Instant; |
... | ... | @@ -40,6 +42,7 @@ import java.time.ZoneOffset; |
40 | 42 | import java.util.*; |
41 | 43 | import java.util.concurrent.ExecutorService; |
42 | 44 | import java.util.concurrent.Executors; |
45 | +import java.util.stream.Collectors; | |
43 | 46 | |
44 | 47 | import static org.apache.commons.lang3.StringUtils.isBlank; |
45 | 48 | |
... | ... | @@ -56,10 +59,10 @@ public class BaseTimeseriesService implements TimeseriesService { |
56 | 59 | private TimeseriesDao timeseriesDao; |
57 | 60 | |
58 | 61 | @Override |
59 | - public ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, TsKvQuery query) { | |
62 | + public ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries) { | |
60 | 63 | validate(entityType, entityId); |
61 | - validate(query); | |
62 | - return timeseriesDao.findAllAsync(entityType, entityId.getId(), query); | |
64 | + queries.forEach(query -> validate(query)); | |
65 | + return timeseriesDao.findAllAsync(entityType, entityId.getId(), queries); | |
63 | 66 | } |
64 | 67 | |
65 | 68 | @Override |
... | ... | @@ -132,7 +135,7 @@ public class BaseTimeseriesService implements TimeseriesService { |
132 | 135 | throw new IncorrectParameterException("TsKvQuery can't be null"); |
133 | 136 | } else if (isBlank(query.getKey())) { |
134 | 137 | throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty"); |
135 | - } else if (query.getAggregation() == null){ | |
138 | + } else if (query.getAggregation() == null) { | |
136 | 139 | throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty"); |
137 | 140 | } |
138 | 141 | } | ... | ... |
... | ... | @@ -33,9 +33,7 @@ public interface TimeseriesDao { |
33 | 33 | |
34 | 34 | long toPartitionTs(long ts); |
35 | 35 | |
36 | - ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query); | |
37 | - | |
38 | -// List<TsKvEntry> find(String entityType, UUID entityId, TsKvQuery query, Optional<Long> minPartition, Optional<Long> maxPartition); | |
36 | + ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries); | |
39 | 37 | |
40 | 38 | ResultSetFuture findLatest(String entityType, UUID entityId, String key); |
41 | 39 | ... | ... |
... | ... | @@ -33,7 +33,7 @@ import java.util.Set; |
33 | 33 | */ |
34 | 34 | public interface TimeseriesService { |
35 | 35 | |
36 | - ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, TsKvQuery query); | |
36 | + ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries); | |
37 | 37 | |
38 | 38 | ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys); |
39 | 39 | ... | ... |
... | ... | @@ -82,9 +82,7 @@ public interface PluginContext { |
82 | 82 | |
83 | 83 | void saveTsData(DeviceId deviceId, List<TsKvEntry> entry, PluginCallback<Void> callback); |
84 | 84 | |
85 | - List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query); | |
86 | - | |
87 | - void loadTimeseries(DeviceId deviceId, TsKvQuery query, PluginCallback<List<TsKvEntry>> callback); | |
85 | + void loadTimeseries(DeviceId deviceId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback); | |
88 | 86 | |
89 | 87 | void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback); |
90 | 88 | ... | ... |
... | ... | @@ -15,9 +15,16 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.extensions.core.plugin.telemetry.cmd; |
17 | 17 | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
20 | +import lombok.NoArgsConstructor; | |
21 | + | |
18 | 22 | /** |
19 | 23 | * @author Andrew Shvayka |
20 | 24 | */ |
25 | +@NoArgsConstructor | |
26 | +@AllArgsConstructor | |
27 | +@Data | |
21 | 28 | public class GetHistoryCmd implements TelemetryPluginCmd { |
22 | 29 | |
23 | 30 | private int cmdId; |
... | ... | @@ -25,46 +32,7 @@ public class GetHistoryCmd implements TelemetryPluginCmd { |
25 | 32 | private String keys; |
26 | 33 | private long startTs; |
27 | 34 | private long endTs; |
35 | + private int limit; | |
36 | + private String agg; | |
28 | 37 | |
29 | - @Override | |
30 | - public int getCmdId() { | |
31 | - return cmdId; | |
32 | - } | |
33 | - | |
34 | - @Override | |
35 | - public void setCmdId(int cmdId) { | |
36 | - this.cmdId = cmdId; | |
37 | - } | |
38 | - | |
39 | - public String getDeviceId() { | |
40 | - return deviceId; | |
41 | - } | |
42 | - | |
43 | - public void setDeviceId(String deviceId) { | |
44 | - this.deviceId = deviceId; | |
45 | - } | |
46 | - | |
47 | - public String getKeys() { | |
48 | - return keys; | |
49 | - } | |
50 | - | |
51 | - public void setKeys(String keys) { | |
52 | - this.keys = keys; | |
53 | - } | |
54 | - | |
55 | - public long getStartTs() { | |
56 | - return startTs; | |
57 | - } | |
58 | - | |
59 | - public void setStartTs(long startTs) { | |
60 | - this.startTs = startTs; | |
61 | - } | |
62 | - | |
63 | - public long getEndTs() { | |
64 | - return endTs; | |
65 | - } | |
66 | - | |
67 | - public void setEndTs(long endTs) { | |
68 | - this.endTs = endTs; | |
69 | - } | |
70 | 38 | } | ... | ... |
... | ... | @@ -16,11 +16,13 @@ |
16 | 16 | package org.thingsboard.server.extensions.core.plugin.telemetry.cmd; |
17 | 17 | |
18 | 18 | import lombok.AllArgsConstructor; |
19 | +import lombok.Data; | |
19 | 20 | import lombok.NoArgsConstructor; |
20 | 21 | import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionType; |
21 | 22 | |
22 | 23 | @NoArgsConstructor |
23 | 24 | @AllArgsConstructor |
25 | +@Data | |
24 | 26 | public abstract class SubscriptionCmd implements TelemetryPluginCmd { |
25 | 27 | |
26 | 28 | private int cmdId; |
... | ... | @@ -31,46 +33,6 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd { |
31 | 33 | |
32 | 34 | public abstract SubscriptionType getType(); |
33 | 35 | |
34 | - public int getCmdId() { | |
35 | - return cmdId; | |
36 | - } | |
37 | - | |
38 | - public void setCmdId(int cmdId) { | |
39 | - this.cmdId = cmdId; | |
40 | - } | |
41 | - | |
42 | - public String getDeviceId() { | |
43 | - return deviceId; | |
44 | - } | |
45 | - | |
46 | - public void setDeviceId(String deviceId) { | |
47 | - this.deviceId = deviceId; | |
48 | - } | |
49 | - | |
50 | - public String getKeys() { | |
51 | - return keys; | |
52 | - } | |
53 | - | |
54 | - public void setTags(String tags) { | |
55 | - this.keys = tags; | |
56 | - } | |
57 | - | |
58 | - public boolean isUnsubscribe() { | |
59 | - return unsubscribe; | |
60 | - } | |
61 | - | |
62 | - public void setUnsubscribe(boolean unsubscribe) { | |
63 | - this.unsubscribe = unsubscribe; | |
64 | - } | |
65 | - | |
66 | - public String getScope() { | |
67 | - return scope; | |
68 | - } | |
69 | - | |
70 | - public void setKeys(String keys) { | |
71 | - this.keys = keys; | |
72 | - } | |
73 | - | |
74 | 36 | @Override |
75 | 37 | public String toString() { |
76 | 38 | return "SubscriptionCmd [deviceId=" + deviceId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]"; | ... | ... |
... | ... | @@ -15,6 +15,8 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.extensions.core.plugin.telemetry.cmd; |
17 | 17 | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
18 | 20 | import lombok.NoArgsConstructor; |
19 | 21 | import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionType; |
20 | 22 | |
... | ... | @@ -22,17 +24,13 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT |
22 | 24 | * @author Andrew Shvayka |
23 | 25 | */ |
24 | 26 | @NoArgsConstructor |
27 | +@AllArgsConstructor | |
28 | +@Data | |
25 | 29 | public class TimeseriesSubscriptionCmd extends SubscriptionCmd { |
26 | 30 | |
27 | 31 | private long timeWindow; |
28 | - | |
29 | - public long getTimeWindow() { | |
30 | - return timeWindow; | |
31 | - } | |
32 | - | |
33 | - public void setTimeWindow(long timeWindow) { | |
34 | - this.timeWindow = timeWindow; | |
35 | - } | |
32 | + private int limit; | |
33 | + private String agg; | |
36 | 34 | |
37 | 35 | @Override |
38 | 36 | public SubscriptionType getType() { | ... | ... |
... | ... | @@ -158,40 +158,14 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { |
158 | 158 | log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), cmd.getDeviceId()); |
159 | 159 | long endTs = System.currentTimeMillis(); |
160 | 160 | startTs = endTs - cmd.getTimeWindow(); |
161 | - for (String key : keys) { | |
162 | - TsKvQuery query = new BaseTsKvQuery(key, startTs, endTs); | |
163 | - data.addAll(ctx.loadTimeseries(deviceId, query)); | |
164 | - } | |
165 | - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); | |
166 | 161 | |
167 | - Map<String, Long> subState = new HashMap<>(keys.size()); | |
168 | - keys.forEach(key -> subState.put(key, startTs)); | |
169 | - data.forEach(v -> subState.put(v.getKey(), v.getTs())); | |
170 | - SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState); | |
171 | - subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); | |
162 | + List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getLimit(), Aggregation.valueOf(cmd.getAgg()))).collect(Collectors.toList()); | |
163 | + ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys)); | |
172 | 164 | } else { |
173 | 165 | List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); |
174 | 166 | startTs = System.currentTimeMillis(); |
175 | 167 | log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), cmd.getDeviceId()); |
176 | - ctx.loadLatestTimeseries(deviceId, keys, new PluginCallback<List<TsKvEntry>>() { | |
177 | - @Override | |
178 | - public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { | |
179 | - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); | |
180 | - | |
181 | - Map<String, Long> subState = new HashMap<>(keys.size()); | |
182 | - keys.forEach(key -> subState.put(key, startTs)); | |
183 | - data.forEach(v -> subState.put(v.getKey(), v.getTs())); | |
184 | - SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState); | |
185 | - subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); | |
186 | - } | |
187 | - | |
188 | - @Override | |
189 | - public void onFailure(PluginContext ctx, Exception e) { | |
190 | - SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, | |
191 | - "Failed to fetch data!"); | |
192 | - sendWsMsg(ctx, sessionRef, update); | |
193 | - } | |
194 | - }); | |
168 | + ctx.loadLatestTimeseries(deviceId, keys, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys)); | |
195 | 169 | } |
196 | 170 | } else { |
197 | 171 | ctx.loadLatestTimeseries(deviceId, new PluginCallback<List<TsKvEntry>>() { |
... | ... | @@ -216,6 +190,28 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { |
216 | 190 | } |
217 | 191 | } |
218 | 192 | |
193 | + private PluginCallback<List<TsKvEntry>> getSubscriptionCallback(final PluginWebsocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final DeviceId deviceId, final long startTs, final List<String> keys) { | |
194 | + return new PluginCallback<List<TsKvEntry>>() { | |
195 | + @Override | |
196 | + public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { | |
197 | + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); | |
198 | + | |
199 | + Map<String, Long> subState = new HashMap<>(keys.size()); | |
200 | + keys.forEach(key -> subState.put(key, startTs)); | |
201 | + data.forEach(v -> subState.put(v.getKey(), v.getTs())); | |
202 | + SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState); | |
203 | + subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub); | |
204 | + } | |
205 | + | |
206 | + @Override | |
207 | + public void onFailure(PluginContext ctx, Exception e) { | |
208 | + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, | |
209 | + "Failed to fetch data!"); | |
210 | + sendWsMsg(ctx, sessionRef, update); | |
211 | + } | |
212 | + }; | |
213 | + } | |
214 | + | |
219 | 215 | private void handleWsHistoryCmd(PluginContext ctx, PluginWebsocketSessionRef sessionRef, GetHistoryCmd cmd) { |
220 | 216 | String sessionId = sessionRef.getSessionId(); |
221 | 217 | WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); |
... | ... | @@ -246,12 +242,19 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { |
246 | 242 | return; |
247 | 243 | } |
248 | 244 | List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); |
249 | - List<TsKvEntry> data = new ArrayList<>(); | |
250 | - for (String key : keys) { | |
251 | - TsKvQuery query = new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs()); | |
252 | - data.addAll(ctx.loadTimeseries(deviceId, query)); | |
253 | - } | |
254 | - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); | |
245 | + List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getLimit(), Aggregation.valueOf(cmd.getAgg()))).collect(Collectors.toList()); | |
246 | + ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() { | |
247 | + @Override | |
248 | + public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { | |
249 | + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); | |
250 | + } | |
251 | + | |
252 | + @Override | |
253 | + public void onFailure(PluginContext ctx, Exception e) { | |
254 | + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, | |
255 | + "Failed to fetch data!")); | |
256 | + } | |
257 | + }); | |
255 | 258 | } |
256 | 259 | |
257 | 260 | private boolean validateSessionMetadata(PluginContext ctx, PluginWebsocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { | ... | ... |