Commit 2accabf0b04db16734d5eeab02fd882abe3c608d

Authored by Andrew Shvayka
1 parent 9328bbc0

Tmp commit

Showing 18 changed files with 501 additions and 179 deletions
@@ -195,9 +195,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso @@ -195,9 +195,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
195 } 195 }
196 196
197 void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { 197 void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
198 - //TODO: improve this procedure to fetch only changed attributes. 198 + //TODO: improve this procedure to fetch only changed attributes and support attributes deletion
199 refreshAttributes(); 199 refreshAttributes();
200 - //TODO: support attributes deletion  
201 Set<AttributeKey> keys = msg.getKeys(); 200 Set<AttributeKey> keys = msg.getKeys();
202 if (attributeSubscriptions.size() > 0) { 201 if (attributeSubscriptions.size() > 0) {
203 ToDeviceMsg notification = null; 202 ToDeviceMsg notification = null;
1 /** 1 /**
2 * Copyright © 2016-2017 The Thingsboard Authors 2 * Copyright © 2016-2017 The Thingsboard Authors
3 - * 3 + * <p>
4 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with 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 6 * You may obtain a copy of the License at
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - * 7 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
10 * Unless required by applicable law or agreed to in writing, software 10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -56,6 +56,7 @@ import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRe @@ -56,6 +56,7 @@ import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRe
56 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; 56 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
57 57
58 import akka.actor.ActorRef; 58 import akka.actor.ActorRef;
  59 +import org.w3c.dom.Attr;
59 60
60 import javax.annotation.Nullable; 61 import javax.annotation.Nullable;
61 62
@@ -91,49 +92,86 @@ public final class PluginProcessingContext implements PluginContext { @@ -91,49 +92,86 @@ public final class PluginProcessingContext implements PluginContext {
91 @Override 92 @Override
92 public void saveAttributes(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) { 93 public void saveAttributes(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) {
93 validate(deviceId); 94 validate(deviceId);
94 - Set<AttributeKey> keys = new HashSet<>();  
95 - for (AttributeKvEntry attribute : attributes) {  
96 - keys.add(new AttributeKey(scope, attribute.getKey()));  
97 - }  
98 95
99 ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes); 96 ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
100 Futures.addCallback(rsListFuture, getListCallback(callback, v -> { 97 Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
101 - onDeviceAttributesChanged(deviceId, keys); 98 + onDeviceAttributesChanged(deviceId, scope, attributes);
102 return null; 99 return null;
103 }), executor); 100 }), executor);
104 } 101 }
105 102
106 @Override 103 @Override
107 - public Optional<AttributeKvEntry> loadAttribute(DeviceId deviceId, String attributeType, String attributeKey) { 104 + public void removeAttributes(DeviceId deviceId, String scope, List<String> keys, PluginCallback<Void> callback) {
108 validate(deviceId); 105 validate(deviceId);
109 - AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);  
110 - return Optional.ofNullable(attribute); 106 + ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys);
  107 + Futures.addCallback(future, getCallback(callback, v -> null), executor);
  108 + onDeviceAttributesDeleted(tenantId, deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
111 } 109 }
112 110
113 @Override 111 @Override
114 - public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType, List<String> attributeKeys) { 112 + public void saveAttributesByDevice(TenantId tenantId, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) {
115 validate(deviceId); 113 validate(deviceId);
116 - List<AttributeKvEntry> result = new ArrayList<>(attributeKeys.size());  
117 - for (String attributeKey : attributeKeys) {  
118 - AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);  
119 - if (attribute != null) {  
120 - result.add(attribute);  
121 - }  
122 - }  
123 - return result; 114 +
  115 + ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
  116 + Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
  117 + onDeviceAttributesChanged(tenantId, deviceId, scope, attributes);
  118 + return null;
  119 + }), executor);
  120 + }
  121 +
  122 + @Override
  123 + public void removeAttributesByDevice(TenantId tenantId, DeviceId deviceId, String scope, List<String> keys, PluginCallback<Void> callback) {
  124 + validate(deviceId);
  125 + ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys);
  126 + Futures.addCallback(future, getCallback(callback, v -> null), executor);
  127 + onDeviceAttributesDeleted(tenantId, deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
  128 + }
  129 +
  130 + @Override
  131 + public void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback) {
  132 + validate(deviceId);
  133 + ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
  134 + Futures.addCallback(future, getCallback(callback, v -> v), executor);
124 } 135 }
125 136
126 @Override 137 @Override
127 - public List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType) { 138 + public void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback) {
128 validate(deviceId); 139 validate(deviceId);
129 - return pluginCtx.attributesService.findAll(deviceId, attributeType); 140 + ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys);
  141 + Futures.addCallback(future, getCallback(callback, v -> v), executor);
130 } 142 }
131 143
132 @Override 144 @Override
133 - public void removeAttributes(DeviceId deviceId, String scope, List<String> keys) { 145 + public void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) {
134 validate(deviceId); 146 validate(deviceId);
135 - pluginCtx.attributesService.removeAll(deviceId, scope, keys);  
136 - onDeviceAttributesDeleted(deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet())); 147 + ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(deviceId, attributeType);
  148 + Futures.addCallback(future, getCallback(callback, v -> v), executor);
  149 + }
  150 +
  151 + @Override
  152 + public void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, PluginCallback<List<AttributeKvEntry>> callback) {
  153 + validate(deviceId);
  154 + List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
  155 + attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(deviceId, attributeType)));
  156 + convertFuturesAndAddCallback(callback, futures);
  157 + }
  158 +
  159 + @Override
  160 + public void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback) {
  161 + validate(deviceId);
  162 + List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
  163 + attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys)));
  164 + convertFuturesAndAddCallback(callback, futures);
  165 + }
  166 +
  167 + private void convertFuturesAndAddCallback(PluginCallback<List<AttributeKvEntry>> callback, List<ListenableFuture<List<AttributeKvEntry>>> futures) {
  168 + ListenableFuture<List<AttributeKvEntry>> future = Futures.transform(Futures.successfulAsList(futures),
  169 + (Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
  170 + List<AttributeKvEntry> result = new ArrayList<>();
  171 + input.forEach(r -> result.addAll(r));
  172 + return result;
  173 + }, executor);
  174 + Futures.addCallback(future, getCallback(callback, v -> v), executor);
137 } 175 }
138 176
139 @Override 177 @Override
@@ -205,18 +243,12 @@ public final class PluginProcessingContext implements PluginContext { @@ -205,18 +243,12 @@ public final class PluginProcessingContext implements PluginContext {
205 return securityCtx; 243 return securityCtx;
206 } 244 }
207 245
208 - private void onDeviceAttributesChanged(DeviceId deviceId, AttributeKey key) {  
209 - onDeviceAttributesChanged(deviceId, Collections.singleton(key)); 246 + private void onDeviceAttributesDeleted(TenantId tenantId, DeviceId deviceId, Set<AttributeKey> keys) {
  247 + pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(tenantId, deviceId, keys));
210 } 248 }
211 249
212 - private void onDeviceAttributesDeleted(DeviceId deviceId, Set<AttributeKey> keys) {  
213 - Device device = pluginCtx.deviceService.findDeviceById(deviceId);  
214 - pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), deviceId, keys));  
215 - }  
216 -  
217 - private void onDeviceAttributesChanged(DeviceId deviceId, Set<AttributeKey> keys) {  
218 - Device device = pluginCtx.deviceService.findDeviceById(deviceId);  
219 - pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(device.getTenantId(), deviceId, keys)); 250 + private void onDeviceAttributesChanged(TenantId tenantId, DeviceId deviceId, String scope, List<AttributeKvEntry> values) {
  251 + pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, deviceId, scope, values));
220 } 252 }
221 253
222 private <T> FutureCallback<List<ResultSet>> getListCallback(final PluginCallback<T> callback, Function<List<ResultSet>, T> transformer) { 254 private <T> FutureCallback<List<ResultSet>> getListCallback(final PluginCallback<T> callback, Function<List<ResultSet>, T> transformer) {
@@ -256,11 +288,12 @@ public final class PluginProcessingContext implements PluginContext { @@ -256,11 +288,12 @@ public final class PluginProcessingContext implements PluginContext {
256 } 288 }
257 289
258 // TODO: replace with our own exceptions 290 // TODO: replace with our own exceptions
259 - private boolean validate(DeviceId deviceId) { 291 + private boolean validate(DeviceId deviceId, PluginCallback<Device> callback) {
260 if (securityCtx.isPresent()) { 292 if (securityCtx.isPresent()) {
261 - PluginApiCallSecurityContext ctx = securityCtx.get(); 293 + final PluginApiCallSecurityContext ctx = securityCtx.get();
262 if (ctx.isTenantAdmin() || ctx.isCustomerUser()) { 294 if (ctx.isTenantAdmin() || ctx.isCustomerUser()) {
263 - Device device = pluginCtx.deviceService.findDeviceById(deviceId); 295 + ListenableFuture<Device> device = pluginCtx.deviceService.findDeviceById(deviceId);
  296 + Futures.addCallback(device, );
264 if (device == null) { 297 if (device == null) {
265 throw new IllegalStateException("Device not found!"); 298 throw new IllegalStateException("Device not found!");
266 } else { 299 } else {
  1 +/**
  2 + * Copyright © 2016-2017 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;
  17 +
  18 +import javax.annotation.PostConstruct;
  19 +import javax.annotation.PreDestroy;
  20 +import java.util.concurrent.ExecutorService;
  21 +import java.util.concurrent.Executors;
  22 +
  23 +/**
  24 + * Created by ashvayka on 21.02.17.
  25 + */
  26 +public abstract class AbstractAsyncDao extends AbstractDao {
  27 +
  28 + protected ExecutorService readResultsProcessingExecutor;
  29 +
  30 + @PostConstruct
  31 + public void startExecutor() {
  32 + readResultsProcessingExecutor = Executors.newCachedThreadPool();
  33 + }
  34 +
  35 + @PreDestroy
  36 + public void stopExecutor() {
  37 + if (readResultsProcessingExecutor != null) {
  38 + readResultsProcessingExecutor.shutdownNow();
  39 + }
  40 + }
  41 +
  42 +}
@@ -15,23 +15,28 @@ @@ -15,23 +15,28 @@
15 */ 15 */
16 package org.thingsboard.server.dao.attributes; 16 package org.thingsboard.server.dao.attributes;
17 17
  18 +import com.datastax.driver.core.ResultSet;
18 import com.datastax.driver.core.ResultSetFuture; 19 import com.datastax.driver.core.ResultSetFuture;
  20 +import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.id.EntityId; 21 import org.thingsboard.server.common.data.id.EntityId;
20 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
21 23
  24 +import java.util.Collection;
22 import java.util.List; 25 import java.util.List;
23 -import java.util.UUID; 26 +import java.util.Optional;
24 27
25 /** 28 /**
26 * @author Andrew Shvayka 29 * @author Andrew Shvayka
27 */ 30 */
28 public interface AttributesDao { 31 public interface AttributesDao {
29 32
30 - AttributeKvEntry find(EntityId entityId, String attributeType, String attributeKey); 33 + ListenableFuture<Optional<AttributeKvEntry>> find(EntityId entityId, String attributeType, String attributeKey);
31 34
32 - List<AttributeKvEntry> findAll(EntityId entityId, String attributeType); 35 + ListenableFuture<List<AttributeKvEntry>> find(EntityId entityId, String attributeType, Collection<String> attributeKey);
  36 +
  37 + ListenableFuture<List<AttributeKvEntry>> findAll(EntityId entityId, String attributeType);
33 38
34 ResultSetFuture save(EntityId entityId, String attributeType, AttributeKvEntry attribute); 39 ResultSetFuture save(EntityId entityId, String attributeType, AttributeKvEntry attribute);
35 40
36 - void removeAll(EntityId entityId, String scope, List<String> keys); 41 + ListenableFuture<List<ResultSet>> removeAll(EntityId entityId, String scope, List<String> keys);
37 } 42 }
@@ -23,18 +23,22 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -23,18 +23,22 @@ import org.thingsboard.server.common.data.id.EntityId;
23 import org.thingsboard.server.common.data.id.UUIDBased; 23 import org.thingsboard.server.common.data.id.UUIDBased;
24 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 24 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
25 25
  26 +import java.util.Collection;
26 import java.util.List; 27 import java.util.List;
  28 +import java.util.Optional;
27 29
28 /** 30 /**
29 * @author Andrew Shvayka 31 * @author Andrew Shvayka
30 */ 32 */
31 public interface AttributesService { 33 public interface AttributesService {
32 34
33 - AttributeKvEntry find(EntityId entityId, String scope, String attributeKey); 35 + ListenableFuture<Optional<AttributeKvEntry>> find(EntityId entityId, String scope, String attributeKey);
34 36
35 - List<AttributeKvEntry> findAll(EntityId entityId, String scope); 37 + ListenableFuture<List<AttributeKvEntry>> find(EntityId entityId, String scope, Collection<String> attributeKeys);
  38 +
  39 + ListenableFuture<List<AttributeKvEntry>> findAll(EntityId entityId, String scope);
36 40
37 ListenableFuture<List<ResultSet>> save(EntityId entityId, String scope, List<AttributeKvEntry> attributes); 41 ListenableFuture<List<ResultSet>> save(EntityId entityId, String scope, List<AttributeKvEntry> attributes);
38 42
39 - void removeAll(EntityId entityId, String scope, List<String> attributeKeys); 43 + ListenableFuture<List<ResultSet>> removeAll(EntityId entityId, String scope, List<String> attributeKeys);
40 } 44 }
@@ -18,19 +18,24 @@ package org.thingsboard.server.dao.attributes; @@ -18,19 +18,24 @@ package org.thingsboard.server.dao.attributes;
18 import com.datastax.driver.core.*; 18 import com.datastax.driver.core.*;
19 import com.datastax.driver.core.querybuilder.QueryBuilder; 19 import com.datastax.driver.core.querybuilder.QueryBuilder;
20 import com.datastax.driver.core.querybuilder.Select; 20 import com.datastax.driver.core.querybuilder.Select;
  21 +import com.google.common.base.Function;
  22 +import com.google.common.util.concurrent.Futures;
  23 +import com.google.common.util.concurrent.ListenableFuture;
21 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
22 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
23 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
24 -import org.thingsboard.server.common.data.kv.DataType;  
25 -import org.thingsboard.server.dao.AbstractDao; 27 +import org.thingsboard.server.dao.AbstractAsyncDao;
26 import org.thingsboard.server.dao.model.ModelConstants; 28 import org.thingsboard.server.dao.model.ModelConstants;
27 -import org.slf4j.Logger;  
28 -import org.slf4j.LoggerFactory;  
29 import org.thingsboard.server.common.data.kv.*; 29 import org.thingsboard.server.common.data.kv.*;
30 import org.thingsboard.server.dao.timeseries.BaseTimeseriesDao; 30 import org.thingsboard.server.dao.timeseries.BaseTimeseriesDao;
31 31
  32 +import javax.annotation.PostConstruct;
  33 +import javax.annotation.PreDestroy;
32 import java.util.ArrayList; 34 import java.util.ArrayList;
  35 +import java.util.Collection;
33 import java.util.List; 36 import java.util.List;
  37 +import java.util.Optional;
  38 +import java.util.stream.Collectors;
34 39
35 import static org.thingsboard.server.dao.model.ModelConstants.*; 40 import static org.thingsboard.server.dao.model.ModelConstants.*;
36 import static com.datastax.driver.core.querybuilder.QueryBuilder.*; 41 import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
@@ -40,29 +45,55 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.*; @@ -40,29 +45,55 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
40 */ 45 */
41 @Component 46 @Component
42 @Slf4j 47 @Slf4j
43 -public class BaseAttributesDao extends AbstractDao implements AttributesDao {  
44 - 48 +public class BaseAttributesDao extends AbstractAsyncDao implements AttributesDao {
  49 +
45 private PreparedStatement saveStmt; 50 private PreparedStatement saveStmt;
46 51
  52 + @PostConstruct
  53 + public void init() {
  54 + super.startExecutor();
  55 + }
  56 +
  57 + @PreDestroy
  58 + public void stop() {
  59 + super.stopExecutor();
  60 + }
  61 +
47 @Override 62 @Override
48 - public AttributeKvEntry find(EntityId entityId, String attributeType, String attributeKey) { 63 + public ListenableFuture<Optional<AttributeKvEntry>> find(EntityId entityId, String attributeType, String attributeKey) {
49 Select.Where select = select().from(ATTRIBUTES_KV_CF) 64 Select.Where select = select().from(ATTRIBUTES_KV_CF)
50 .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) 65 .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType()))
51 .and(eq(ENTITY_ID_COLUMN, entityId.getId())) 66 .and(eq(ENTITY_ID_COLUMN, entityId.getId()))
52 .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) 67 .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType))
53 .and(eq(ATTRIBUTE_KEY_COLUMN, attributeKey)); 68 .and(eq(ATTRIBUTE_KEY_COLUMN, attributeKey));
54 log.trace("Generated query [{}] for entityId {} and key {}", select, entityId, attributeKey); 69 log.trace("Generated query [{}] for entityId {} and key {}", select, entityId, attributeKey);
55 - return convertResultToAttributesKvEntry(attributeKey, executeRead(select).one()); 70 + return Futures.transform(executeAsyncRead(select), (Function<? super ResultSet, ? extends Optional<AttributeKvEntry>>) input ->
  71 + Optional.of(convertResultToAttributesKvEntry(attributeKey, input.one()))
  72 + , readResultsProcessingExecutor);
56 } 73 }
57 74
58 @Override 75 @Override
59 - public List<AttributeKvEntry> findAll(EntityId entityId, String attributeType) { 76 + public ListenableFuture<List<AttributeKvEntry>> find(EntityId entityId, String attributeType, Collection<String> attributeKeys) {
  77 + List<ListenableFuture<Optional<AttributeKvEntry>>> entries = new ArrayList<>();
  78 + attributeKeys.forEach(attributeKey -> entries.add(find(entityId, attributeType, attributeKey)));
  79 + return Futures.transform(Futures.allAsList(entries), (Function<List<Optional<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
  80 + List<AttributeKvEntry> result = new ArrayList<>();
  81 + input.stream().filter(opt -> opt.isPresent()).forEach(opt -> result.add(opt.get()));
  82 + return result;
  83 + }, readResultsProcessingExecutor);
  84 + }
  85 +
  86 +
  87 + @Override
  88 + public ListenableFuture<List<AttributeKvEntry>> findAll(EntityId entityId, String attributeType) {
60 Select.Where select = select().from(ATTRIBUTES_KV_CF) 89 Select.Where select = select().from(ATTRIBUTES_KV_CF)
61 .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) 90 .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType()))
62 .and(eq(ENTITY_ID_COLUMN, entityId.getId())) 91 .and(eq(ENTITY_ID_COLUMN, entityId.getId()))
63 .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)); 92 .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType));
64 log.trace("Generated query [{}] for entityId {} and attributeType {}", select, entityId, attributeType); 93 log.trace("Generated query [{}] for entityId {} and attributeType {}", select, entityId, attributeType);
65 - return convertResultToAttributesKvEntryList(executeRead(select)); 94 + return Futures.transform(executeAsyncRead(select), (Function<? super ResultSet, ? extends List<AttributeKvEntry>>) input ->
  95 + convertResultToAttributesKvEntryList(input)
  96 + , readResultsProcessingExecutor);
66 } 97 }
67 98
68 @Override 99 @Override
@@ -93,20 +124,19 @@ public class BaseAttributesDao extends AbstractDao implements AttributesDao { @@ -93,20 +124,19 @@ public class BaseAttributesDao extends AbstractDao implements AttributesDao {
93 } 124 }
94 125
95 @Override 126 @Override
96 - public void removeAll(EntityId entityId, String attributeType, List<String> keys) {  
97 - for (String key : keys) {  
98 - delete(entityId, attributeType, key);  
99 - } 127 + public ListenableFuture<List<ResultSet>> removeAll(EntityId entityId, String attributeType, List<String> keys) {
  128 + List<ResultSetFuture> futures = keys.stream().map(key -> delete(entityId, attributeType, key)).collect(Collectors.toList());
  129 + return Futures.allAsList(futures);
100 } 130 }
101 131
102 - private void delete(EntityId entityId, String attributeType, String key) { 132 + private ResultSetFuture delete(EntityId entityId, String attributeType, String key) {
103 Statement delete = QueryBuilder.delete().all().from(ModelConstants.ATTRIBUTES_KV_CF) 133 Statement delete = QueryBuilder.delete().all().from(ModelConstants.ATTRIBUTES_KV_CF)
104 .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) 134 .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType()))
105 .and(eq(ENTITY_ID_COLUMN, entityId.getId())) 135 .and(eq(ENTITY_ID_COLUMN, entityId.getId()))
106 .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) 136 .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType))
107 .and(eq(ATTRIBUTE_KEY_COLUMN, key)); 137 .and(eq(ATTRIBUTE_KEY_COLUMN, key));
108 log.debug("Remove request: {}", delete.toString()); 138 log.debug("Remove request: {}", delete.toString());
109 - getSession().execute(delete); 139 + return getSession().executeAsync(delete);
110 } 140 }
111 141
112 private PreparedStatement getSaveStmt() { 142 private PreparedStatement getSaveStmt() {
@@ -150,5 +180,4 @@ public class BaseAttributesDao extends AbstractDao implements AttributesDao { @@ -150,5 +180,4 @@ public class BaseAttributesDao extends AbstractDao implements AttributesDao {
150 } 180 }
151 return entries; 181 return entries;
152 } 182 }
153 -  
154 } 183 }
@@ -27,7 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -27,7 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired;
27 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
28 import org.thingsboard.server.dao.service.Validator; 28 import org.thingsboard.server.dao.service.Validator;
29 29
  30 +import java.util.Collection;
30 import java.util.List; 31 import java.util.List;
  32 +import java.util.Optional;
31 33
32 /** 34 /**
33 * @author Andrew Shvayka 35 * @author Andrew Shvayka
@@ -39,14 +41,21 @@ public class BaseAttributesService implements AttributesService { @@ -39,14 +41,21 @@ public class BaseAttributesService implements AttributesService {
39 private AttributesDao attributesDao; 41 private AttributesDao attributesDao;
40 42
41 @Override 43 @Override
42 - public AttributeKvEntry find(EntityId entityId, String scope, String attributeKey) { 44 + public ListenableFuture<Optional<AttributeKvEntry>> find(EntityId entityId, String scope, String attributeKey) {
43 validate(entityId, scope); 45 validate(entityId, scope);
44 Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); 46 Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey);
45 return attributesDao.find(entityId, scope, attributeKey); 47 return attributesDao.find(entityId, scope, attributeKey);
46 } 48 }
47 49
48 @Override 50 @Override
49 - public List<AttributeKvEntry> findAll(EntityId entityId, String scope) { 51 + public ListenableFuture<List<AttributeKvEntry>> find(EntityId entityId, String scope, Collection<String> attributeKeys) {
  52 + validate(entityId, scope);
  53 + attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey));
  54 + return attributesDao.find(entityId, scope, attributeKeys);
  55 + }
  56 +
  57 + @Override
  58 + public ListenableFuture<List<AttributeKvEntry>> findAll(EntityId entityId, String scope) {
50 validate(entityId, scope); 59 validate(entityId, scope);
51 return attributesDao.findAll(entityId, scope); 60 return attributesDao.findAll(entityId, scope);
52 } 61 }
@@ -56,16 +65,16 @@ public class BaseAttributesService implements AttributesService { @@ -56,16 +65,16 @@ public class BaseAttributesService implements AttributesService {
56 validate(entityId, scope); 65 validate(entityId, scope);
57 attributes.forEach(attribute -> validate(attribute)); 66 attributes.forEach(attribute -> validate(attribute));
58 List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(attributes.size()); 67 List<ResultSetFuture> futures = Lists.newArrayListWithExpectedSize(attributes.size());
59 - for(AttributeKvEntry attribute : attributes) { 68 + for (AttributeKvEntry attribute : attributes) {
60 futures.add(attributesDao.save(entityId, scope, attribute)); 69 futures.add(attributesDao.save(entityId, scope, attribute));
61 } 70 }
62 return Futures.allAsList(futures); 71 return Futures.allAsList(futures);
63 } 72 }
64 73
65 @Override 74 @Override
66 - public void removeAll(EntityId entityId, String scope, List<String> keys) { 75 + public ListenableFuture<List<ResultSet>> removeAll(EntityId entityId, String scope, List<String> keys) {
67 validate(entityId, scope); 76 validate(entityId, scope);
68 - attributesDao.removeAll(entityId, scope, keys); 77 + return attributesDao.removeAll(entityId, scope, keys);
69 } 78 }
70 79
71 private static void validate(EntityId id, String scope) { 80 private static void validate(EntityId id, String scope) {
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.device; 16 package org.thingsboard.server.dao.device;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.Device; 19 import org.thingsboard.server.common.data.Device;
19 import org.thingsboard.server.common.data.id.CustomerId; 20 import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.DeviceId; 21 import org.thingsboard.server.common.data.id.DeviceId;
@@ -28,6 +29,8 @@ public interface DeviceService { @@ -28,6 +29,8 @@ public interface DeviceService {
28 29
29 Device findDeviceById(DeviceId deviceId); 30 Device findDeviceById(DeviceId deviceId);
30 31
  32 + ListenableFuture<Device> findDeviceByIdAsync(DeviceId deviceId);
  33 +
31 Optional<Device> findDeviceByTenantIdAndName(TenantId tenantId, String name); 34 Optional<Device> findDeviceByTenantIdAndName(TenantId tenantId, String name);
32 35
33 Device saveDevice(Device device); 36 Device saveDevice(Device device);
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value; @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
28 import org.springframework.stereotype.Component; 28 import org.springframework.stereotype.Component;
29 import org.thingsboard.server.common.data.kv.*; 29 import org.thingsboard.server.common.data.kv.*;
30 import org.thingsboard.server.common.data.kv.DataType; 30 import org.thingsboard.server.common.data.kv.DataType;
  31 +import org.thingsboard.server.dao.AbstractAsyncDao;
31 import org.thingsboard.server.dao.AbstractDao; 32 import org.thingsboard.server.dao.AbstractDao;
32 import org.thingsboard.server.dao.model.ModelConstants; 33 import org.thingsboard.server.dao.model.ModelConstants;
33 34
@@ -50,7 +51,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.select; @@ -50,7 +51,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
50 */ 51 */
51 @Component 52 @Component
52 @Slf4j 53 @Slf4j
53 -public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao { 54 +public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao {
54 55
55 @Value("${cassandra.query.min_aggregation_step_ms}") 56 @Value("${cassandra.query.min_aggregation_step_ms}")
56 private int minAggregationStepMs; 57 private int minAggregationStepMs;
@@ -60,8 +61,6 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao { @@ -60,8 +61,6 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao {
60 61
61 private TsPartitionDate tsFormat; 62 private TsPartitionDate tsFormat;
62 63
63 - private ExecutorService readResultsProcessingExecutor;  
64 -  
65 private PreparedStatement partitionInsertStmt; 64 private PreparedStatement partitionInsertStmt;
66 private PreparedStatement[] latestInsertStmts; 65 private PreparedStatement[] latestInsertStmts;
67 private PreparedStatement[] saveStmts; 66 private PreparedStatement[] saveStmts;
@@ -71,8 +70,8 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao { @@ -71,8 +70,8 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao {
71 70
72 @PostConstruct 71 @PostConstruct
73 public void init() { 72 public void init() {
  73 + super.startExecutor();
74 getFetchStmt(Aggregation.NONE); 74 getFetchStmt(Aggregation.NONE);
75 - readResultsProcessingExecutor = Executors.newCachedThreadPool();  
76 Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); 75 Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
77 if (partition.isPresent()) { 76 if (partition.isPresent()) {
78 tsFormat = partition.get(); 77 tsFormat = partition.get();
@@ -84,9 +83,7 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao { @@ -84,9 +83,7 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao {
84 83
85 @PreDestroy 84 @PreDestroy
86 public void stop() { 85 public void stop() {
87 - if (readResultsProcessingExecutor != null) {  
88 - readResultsProcessingExecutor.shutdownNow();  
89 - } 86 + super.stopExecutor();
90 } 87 }
91 88
92 @Override 89 @Override
@@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
32 32
33 import java.util.Collections; 33 import java.util.Collections;
34 import java.util.List; 34 import java.util.List;
  35 +import java.util.Optional;
35 36
36 import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; 37 import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
37 import static org.thingsboard.server.common.data.DataConstants.DEVICE; 38 import static org.thingsboard.server.common.data.DataConstants.DEVICE;
@@ -54,8 +55,9 @@ public class BaseAttributesServiceTest extends AbstractServiceTest { @@ -54,8 +55,9 @@ public class BaseAttributesServiceTest extends AbstractServiceTest {
54 KvEntry attrValue = new StringDataEntry("attribute1", "value1"); 55 KvEntry attrValue = new StringDataEntry("attribute1", "value1");
55 AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L); 56 AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L);
56 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attr)).get(); 57 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attr)).get();
57 - AttributeKvEntry saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attr.getKey());  
58 - Assert.assertEquals(attr, saved); 58 + Optional<AttributeKvEntry> saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attr.getKey()).get();
  59 + Assert.assertTrue(saved.isPresent());
  60 + Assert.assertEquals(attr, saved.get());
59 } 61 }
60 62
61 @Test 63 @Test
@@ -65,15 +67,17 @@ public class BaseAttributesServiceTest extends AbstractServiceTest { @@ -65,15 +67,17 @@ public class BaseAttributesServiceTest extends AbstractServiceTest {
65 AttributeKvEntry attrOld = new BaseAttributeKvEntry(attrOldValue, 42L); 67 AttributeKvEntry attrOld = new BaseAttributeKvEntry(attrOldValue, 42L);
66 68
67 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrOld)).get(); 69 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrOld)).get();
68 - AttributeKvEntry saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey());  
69 - Assert.assertEquals(attrOld, saved); 70 + Optional<AttributeKvEntry> saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey()).get();
  71 +
  72 + Assert.assertTrue(saved.isPresent());
  73 + Assert.assertEquals(attrOld, saved.get());
70 74
71 KvEntry attrNewValue = new StringDataEntry("attribute1", "value2"); 75 KvEntry attrNewValue = new StringDataEntry("attribute1", "value2");
72 AttributeKvEntry attrNew = new BaseAttributeKvEntry(attrNewValue, 73L); 76 AttributeKvEntry attrNew = new BaseAttributeKvEntry(attrNewValue, 73L);
73 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrNew)).get(); 77 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrNew)).get();
74 78
75 - saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey());  
76 - Assert.assertEquals(attrNew, saved); 79 + saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey()).get();
  80 + Assert.assertEquals(attrNew, saved.get());
77 } 81 }
78 82
79 @Test 83 @Test
@@ -91,7 +95,7 @@ public class BaseAttributesServiceTest extends AbstractServiceTest { @@ -91,7 +95,7 @@ public class BaseAttributesServiceTest extends AbstractServiceTest {
91 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrANew)).get(); 95 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrANew)).get();
92 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrBNew)).get(); 96 attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrBNew)).get();
93 97
94 - List<AttributeKvEntry> saved = attributesService.findAll(deviceId, DataConstants.CLIENT_SCOPE); 98 + List<AttributeKvEntry> saved = attributesService.findAll(deviceId, DataConstants.CLIENT_SCOPE).get();
95 99
96 Assert.assertNotNull(saved); 100 Assert.assertNotNull(saved);
97 Assert.assertEquals(2, saved.size()); 101 Assert.assertEquals(2, saved.size());
@@ -114,8 +114,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest { @@ -114,8 +114,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
114 entries.add(save(deviceId, 45000, 500)); 114 entries.add(save(deviceId, 45000, 500));
115 entries.add(save(deviceId, 55000, 600)); 115 entries.add(save(deviceId, 55000, 600));
116 116
117 - List<TsKvEntry> list = tsService.findAll(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(LONG_KEY, 0,  
118 - 60000, 3, Aggregation.NONE)).get(); 117 + List<TsKvEntry> list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  118 + 60000, 3, Aggregation.NONE))).get();
119 assertEquals(3, list.size()); 119 assertEquals(3, list.size());
120 assertEquals(55000, list.get(0).getTs()); 120 assertEquals(55000, list.get(0).getTs());
121 assertEquals(java.util.Optional.of(600L), list.get(0).getLongValue()); 121 assertEquals(java.util.Optional.of(600L), list.get(0).getLongValue());
@@ -126,8 +126,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest { @@ -126,8 +126,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
126 assertEquals(35000, list.get(2).getTs()); 126 assertEquals(35000, list.get(2).getTs());
127 assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue()); 127 assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
128 128
129 - list = tsService.findAll(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(LONG_KEY, 0,  
130 - 60000, 3, Aggregation.AVG)).get(); 129 + list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  130 + 60000, 3, Aggregation.AVG))).get();
131 assertEquals(3, list.size()); 131 assertEquals(3, list.size());
132 assertEquals(10000, list.get(0).getTs()); 132 assertEquals(10000, list.get(0).getTs());
133 assertEquals(java.util.Optional.of(150L), list.get(0).getLongValue()); 133 assertEquals(java.util.Optional.of(150L), list.get(0).getLongValue());
@@ -138,8 +138,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest { @@ -138,8 +138,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
138 assertEquals(50000, list.get(2).getTs()); 138 assertEquals(50000, list.get(2).getTs());
139 assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue()); 139 assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
140 140
141 - list = tsService.findAll(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(LONG_KEY, 0,  
142 - 60000, 3, Aggregation.SUM)).get(); 141 + list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  142 + 60000, 3, Aggregation.SUM))).get();
143 143
144 assertEquals(3, list.size()); 144 assertEquals(3, list.size());
145 assertEquals(10000, list.get(0).getTs()); 145 assertEquals(10000, list.get(0).getTs());
@@ -151,8 +151,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest { @@ -151,8 +151,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
151 assertEquals(50000, list.get(2).getTs()); 151 assertEquals(50000, list.get(2).getTs());
152 assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue()); 152 assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
153 153
154 - list = tsService.findAll(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(LONG_KEY, 0,  
155 - 60000, 3, Aggregation.MIN)).get(); 154 + list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  155 + 60000, 3, Aggregation.MIN))).get();
156 156
157 assertEquals(3, list.size()); 157 assertEquals(3, list.size());
158 assertEquals(10000, list.get(0).getTs()); 158 assertEquals(10000, list.get(0).getTs());
@@ -164,8 +164,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest { @@ -164,8 +164,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
164 assertEquals(50000, list.get(2).getTs()); 164 assertEquals(50000, list.get(2).getTs());
165 assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue()); 165 assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
166 166
167 - list = tsService.findAll(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(LONG_KEY, 0,  
168 - 60000, 3, Aggregation.MAX)).get(); 167 + list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  168 + 60000, 3, Aggregation.MAX))).get();
169 169
170 assertEquals(3, list.size()); 170 assertEquals(3, list.size());
171 assertEquals(10000, list.get(0).getTs()); 171 assertEquals(10000, list.get(0).getTs());
@@ -177,8 +177,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest { @@ -177,8 +177,8 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
177 assertEquals(50000, list.get(2).getTs()); 177 assertEquals(50000, list.get(2).getTs());
178 assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue()); 178 assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
179 179
180 - list = tsService.findAll(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(LONG_KEY, 0,  
181 - 60000, 3, Aggregation.COUNT)).get(); 180 + list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  181 + 60000, 3, Aggregation.COUNT))).get();
182 182
183 assertEquals(3, list.size()); 183 assertEquals(3, list.size());
184 assertEquals(10000, list.get(0).getTs()); 184 assertEquals(10000, list.get(0).getTs());
@@ -2,7 +2,7 @@ cassandra.cluster_name=Thingsboard Cluster @@ -2,7 +2,7 @@ cassandra.cluster_name=Thingsboard Cluster
2 2
3 cassandra.keyspace_name=thingsboard 3 cassandra.keyspace_name=thingsboard
4 4
5 -cassandra.url=127.0.0.1:9042 5 +cassandra.url=127.0.0.1:9142
6 6
7 cassandra.ssl=false 7 cassandra.ssl=false
8 8
@@ -94,13 +94,21 @@ public interface PluginContext { @@ -94,13 +94,21 @@ public interface PluginContext {
94 94
95 void saveAttributes(DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback); 95 void saveAttributes(DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
96 96
97 - Optional<AttributeKvEntry> loadAttribute(DeviceId deviceId, String attributeType, String attributeKey); 97 + void removeAttributes(DeviceId deviceId, String scope, List<String> attributeKeys, PluginCallback<Void> callback);
98 98
99 - List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType, List<String> attributeKeys); 99 + void saveAttributesByDevice(TenantId tenantId, DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
100 100
101 - List<AttributeKvEntry> loadAttributes(DeviceId deviceId, String attributeType); 101 + void removeAttributesByDevice(TenantId tenantId, DeviceId deviceId, String scope, List<String> attributeKeys, PluginCallback<Void> callback);
102 102
103 - void removeAttributes(DeviceId deviceId, String scope, List<String> attributeKeys); 103 + void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback);
  104 +
  105 + void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback);
  106 +
  107 + void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback);
  108 +
  109 + void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, PluginCallback<List<AttributeKvEntry>> callback);
  110 +
  111 + void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback);
104 112
105 void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback); 113 void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback);
106 } 114 }
@@ -15,12 +15,14 @@ @@ -15,12 +15,14 @@
15 */ 15 */
16 package org.thingsboard.server.extensions.core.plugin.telemetry; 16 package org.thingsboard.server.extensions.core.plugin.telemetry;
17 17
  18 +import com.sun.javafx.collections.MappingChange;
18 import lombok.Setter; 19 import lombok.Setter;
19 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
20 import org.thingsboard.server.common.data.DataConstants; 21 import org.thingsboard.server.common.data.DataConstants;
21 import org.thingsboard.server.common.data.id.DeviceId; 22 import org.thingsboard.server.common.data.id.DeviceId;
22 import org.thingsboard.server.common.data.kv.*; 23 import org.thingsboard.server.common.data.kv.*;
23 import org.thingsboard.server.common.msg.cluster.ServerAddress; 24 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  25 +import org.thingsboard.server.extensions.api.plugins.PluginCallback;
24 import org.thingsboard.server.extensions.api.plugins.PluginContext; 26 import org.thingsboard.server.extensions.api.plugins.PluginContext;
25 import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryRpcMsgHandler; 27 import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryRpcMsgHandler;
26 import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryWebsocketMsgHandler; 28 import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryWebsocketMsgHandler;
@@ -66,28 +68,49 @@ public class SubscriptionManager { @@ -66,28 +68,49 @@ public class SubscriptionManager {
66 DeviceId deviceId = subscription.getDeviceId(); 68 DeviceId deviceId = subscription.getDeviceId();
67 log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), deviceId, address); 69 log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), deviceId, address);
68 registerSubscription(sessionId, deviceId, subscription); 70 registerSubscription(sessionId, deviceId, subscription);
69 - List<TsKvEntry> missedUpdates = new ArrayList<>();  
70 if (subscription.getType() == SubscriptionType.ATTRIBUTES) { 71 if (subscription.getType() == SubscriptionType.ATTRIBUTES) {
71 - subscription.getKeyStates().entrySet().forEach(e -> {  
72 - Optional<AttributeKvEntry> latestOpt = ctx.loadAttribute(deviceId, DataConstants.CLIENT_SCOPE, e.getKey());  
73 - if (latestOpt.isPresent()) {  
74 - AttributeKvEntry latestEntry = latestOpt.get();  
75 - if (latestEntry.getLastUpdateTs() > e.getValue()) {  
76 - missedUpdates.add(new BasicTsKvEntry(latestEntry.getLastUpdateTs(), latestEntry));  
77 - } 72 + final Map<String, Long> keyStates = subscription.getKeyStates();
  73 + ctx.loadAttributes(deviceId, DataConstants.CLIENT_SCOPE, keyStates.keySet(), new PluginCallback<List<AttributeKvEntry>>() {
  74 + @Override
  75 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> values) {
  76 + List<TsKvEntry> missedUpdates = new ArrayList<>();
  77 + values.forEach(latestEntry -> {
  78 + if (latestEntry.getLastUpdateTs() > keyStates.get(latestEntry.getKey())) {
  79 + missedUpdates.add(new BasicTsKvEntry(latestEntry.getLastUpdateTs(), latestEntry));
78 } 80 }
  81 + });
  82 + if (!missedUpdates.isEmpty()) {
  83 + rpcHandler.onSubscriptionUpdate(ctx, address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));
79 } 84 }
80 - ); 85 + }
  86 +
  87 + @Override
  88 + public void onFailure(PluginContext ctx, Exception e) {
  89 + log.error("Failed to fetch missed updates.", e);
  90 + }
  91 + });
81 } else if (subscription.getType() == SubscriptionType.TIMESERIES) { 92 } else if (subscription.getType() == SubscriptionType.TIMESERIES) {
82 long curTs = System.currentTimeMillis(); 93 long curTs = System.currentTimeMillis();
  94 + List<TsKvQuery> queries = new ArrayList<>();
83 subscription.getKeyStates().entrySet().forEach(e -> { 95 subscription.getKeyStates().entrySet().forEach(e -> {
84 - TsKvQuery query = new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs);  
85 - missedUpdates.addAll(ctx.loadTimeseries(deviceId, query)); 96 + queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
  97 + });
  98 +
  99 + ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
  100 + @Override
  101 + public void onSuccess(PluginContext ctx, List<TsKvEntry> missedUpdates) {
  102 + if (!missedUpdates.isEmpty()) {
  103 + rpcHandler.onSubscriptionUpdate(ctx, address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));
  104 + }
  105 + }
  106 +
  107 + @Override
  108 + public void onFailure(PluginContext ctx, Exception e) {
  109 + log.error("Failed to fetch missed updates.", e);
  110 + }
86 }); 111 });
87 } 112 }
88 - if (!missedUpdates.isEmpty()) {  
89 - rpcHandler.onSubscriptionUpdate(ctx, address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates));  
90 - } 113 +
91 } 114 }
92 115
93 private void registerSubscription(String sessionId, DeviceId deviceId, Subscription subscription) { 116 private void registerSubscription(String sessionId, DeviceId deviceId, Subscription subscription) {
  1 +/**
  2 + * Copyright © 2016-2017 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.extensions.core.plugin.telemetry.handlers;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.extensions.api.plugins.PluginCallback;
  20 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  21 +
  22 +/**
  23 + * Created by ashvayka on 21.02.17.
  24 + */
  25 +@Slf4j
  26 +public abstract class BiPluginCallBack<V1, V2> {
  27 +
  28 + private V1 v1;
  29 + private V2 v2;
  30 +
  31 + public PluginCallback<V1> getV1Callback() {
  32 + return new PluginCallback<V1>() {
  33 + @Override
  34 + public void onSuccess(PluginContext ctx, V1 value) {
  35 + synchronized (BiPluginCallBack.this) {
  36 + BiPluginCallBack.this.v1 = value;
  37 + if (v2 != null) {
  38 + BiPluginCallBack.this.onSuccess(ctx, v1, v2);
  39 + }
  40 + }
  41 + }
  42 +
  43 + @Override
  44 + public void onFailure(PluginContext ctx, Exception e) {
  45 + BiPluginCallBack.this.onFailure(ctx, e);
  46 + }
  47 + };
  48 + }
  49 +
  50 + public PluginCallback<V2> getV2Callback() {
  51 + return new PluginCallback<V2>() {
  52 + @Override
  53 + public void onSuccess(PluginContext ctx, V2 value) {
  54 + synchronized (BiPluginCallBack.this) {
  55 + BiPluginCallBack.this.v2 = value;
  56 + if (v1 != null) {
  57 + BiPluginCallBack.this.onSuccess(ctx, v1, v2);
  58 + }
  59 + }
  60 +
  61 + }
  62 +
  63 + @Override
  64 + public void onFailure(PluginContext ctx, Exception e) {
  65 + BiPluginCallBack.this.onFailure(ctx, e);
  66 + }
  67 + };
  68 + }
  69 +
  70 + abstract public void onSuccess(PluginContext ctx, V1 v1, V2 v2);
  71 +
  72 + abstract public void onFailure(PluginContext ctx, Exception e);
  73 +
  74 +}
@@ -77,41 +77,58 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { @@ -77,41 +77,58 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
77 } 77 }
78 }); 78 });
79 } else if (entity.equals("attributes")) { 79 } else if (entity.equals("attributes")) {
80 - List<AttributeKvEntry> attributes; 80 + PluginCallback<List<AttributeKvEntry>> callback = getAttributeKeysPluginCallback(msg);
81 if (!StringUtils.isEmpty(scope)) { 81 if (!StringUtils.isEmpty(scope)) {
82 - attributes = ctx.loadAttributes(deviceId, scope); 82 + ctx.loadAttributes(deviceId, scope, callback);
83 } else { 83 } else {
84 - attributes = new ArrayList<>();  
85 - Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> attributes.addAll(ctx.loadAttributes(deviceId, s))); 84 + ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
86 } 85 }
87 - List<String> keys = attributes.stream().map(attrKv -> attrKv.getKey()).collect(Collectors.toList());  
88 - msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK));  
89 } 86 }
90 } else if (method.equals("values")) { 87 } else if (method.equals("values")) {
91 if ("timeseries".equals(entity)) { 88 if ("timeseries".equals(entity)) {
92 - String keys = request.getParameter("keys"); 89 + String keysStr = request.getParameter("keys");
93 Optional<Long> startTs = request.getLongParamValue("startTs"); 90 Optional<Long> startTs = request.getLongParamValue("startTs");
94 Optional<Long> endTs = request.getLongParamValue("endTs"); 91 Optional<Long> endTs = request.getLongParamValue("endTs");
95 Optional<Integer> limit = request.getIntParamValue("limit"); 92 Optional<Integer> limit = request.getIntParamValue("limit");
96 - Map<String, List<TsData>> data = new LinkedHashMap<>();  
97 - for (String key : keys.split(",")) {  
98 - //TODO: refactoring  
99 -// List<TsKvEntry> entries = ctx.loadTimeseries(deviceId, new BaseTsKvQuery(key, startTs, endTs, limit));  
100 -// data.put(key, entries.stream().map(v -> new TsData(v.getTs(), v.getValueAsString())).collect(Collectors.toList()));  
101 - }  
102 - msg.getResponseHolder().setResult(new ResponseEntity<>(data, HttpStatus.OK)); 93 + Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name()));
  94 +
  95 + List<String> keys = Arrays.asList(keysStr.split(","));
  96 + List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), limit.get(), agg)).collect(Collectors.toList());
  97 + ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
  98 + @Override
  99 + public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
  100 + Map<String, List<TsData>> result = new LinkedHashMap<>();
  101 + for (TsKvEntry entry : data) {
  102 + result.put(entry.getKey(), data.stream().map(v -> new TsData(v.getTs(), v.getValueAsString())).collect(Collectors.toList()));
  103 + }
  104 + msg.getResponseHolder().setResult(new ResponseEntity<>(data, HttpStatus.OK));
  105 + }
  106 +
  107 + @Override
  108 + public void onFailure(PluginContext ctx, Exception e) {
  109 + log.error("Failed to fetch historical data", e);
  110 + msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  111 + }
  112 + });
103 } else if ("attributes".equals(entity)) { 113 } else if ("attributes".equals(entity)) {
104 String keys = request.getParameter("keys", ""); 114 String keys = request.getParameter("keys", "");
105 - List<AttributeKvEntry> attributes; 115 +
  116 + PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg);
106 if (!StringUtils.isEmpty(scope)) { 117 if (!StringUtils.isEmpty(scope)) {
107 - attributes = getAttributeKvEntries(ctx, scope, deviceId, keys); 118 + if (!StringUtils.isEmpty(keys)) {
  119 + List<String> keyList = Arrays.asList(keys.split(","));
  120 + ctx.loadAttributes(deviceId, scope, keyList, callback);
  121 + } else {
  122 + ctx.loadAttributes(deviceId, scope, callback);
  123 + }
108 } else { 124 } else {
109 - attributes = new ArrayList<>();  
110 - Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> attributes.addAll(getAttributeKvEntries(ctx, s, deviceId, keys))); 125 + if (!StringUtils.isEmpty(keys)) {
  126 + List<String> keyList = Arrays.asList(keys.split(","));
  127 + ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), keyList, callback);
  128 + } else {
  129 + ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
  130 + }
111 } 131 }
112 - List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),  
113 - attribute.getKey(), attribute.getValue())).collect(Collectors.toList());  
114 - msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK));  
115 } 132 }
116 } 133 }
117 } else { 134 } else {
@@ -156,6 +173,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { @@ -156,6 +173,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
156 173
157 @Override 174 @Override
158 public void onFailure(PluginContext ctx, Exception e) { 175 public void onFailure(PluginContext ctx, Exception e) {
  176 + log.error("Failed to save attributes", e);
159 msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); 177 msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
160 } 178 }
161 }); 179 });
@@ -184,8 +202,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { @@ -184,8 +202,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
184 String keysParam = request.getParameter("keys"); 202 String keysParam = request.getParameter("keys");
185 if (!StringUtils.isEmpty(keysParam)) { 203 if (!StringUtils.isEmpty(keysParam)) {
186 String[] keys = keysParam.split(","); 204 String[] keys = keysParam.split(",");
187 - ctx.removeAttributes(deviceId, scope, Arrays.asList(keys));  
188 - msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); 205 + ctx.removeAttributes(deviceId, scope, Arrays.asList(keys), new PluginCallback<Void>() {
  206 + @Override
  207 + public void onSuccess(PluginContext ctx, Void value) {
  208 + msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
  209 + }
  210 +
  211 + @Override
  212 + public void onFailure(PluginContext ctx, Exception e) {
  213 + log.error("Failed to remove attributes", e);
  214 + msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  215 + }
  216 + });
189 return; 217 return;
190 } 218 }
191 } 219 }
@@ -196,14 +224,37 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { @@ -196,14 +224,37 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
196 msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); 224 msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
197 } 225 }
198 226
199 - private List<AttributeKvEntry> getAttributeKvEntries(PluginContext ctx, String scope, DeviceId deviceId, String keysParam) {  
200 - List<AttributeKvEntry> attributes;  
201 - if (!StringUtils.isEmpty(keysParam)) {  
202 - String[] keys = keysParam.split(",");  
203 - attributes = ctx.loadAttributes(deviceId, scope, Arrays.asList(keys));  
204 - } else {  
205 - attributes = ctx.loadAttributes(deviceId, scope);  
206 - }  
207 - return attributes; 227 +
  228 + private PluginCallback<List<AttributeKvEntry>> getAttributeKeysPluginCallback(final PluginRestMsg msg) {
  229 + return new PluginCallback<List<AttributeKvEntry>>() {
  230 + @Override
  231 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) {
  232 + List<String> keys = attributes.stream().map(attrKv -> attrKv.getKey()).collect(Collectors.toList());
  233 + msg.getResponseHolder().setResult(new ResponseEntity<>(keys, HttpStatus.OK));
  234 + }
  235 +
  236 + @Override
  237 + public void onFailure(PluginContext ctx, Exception e) {
  238 + log.error("Failed to fetch attributes", e);
  239 + msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  240 + }
  241 + };
  242 + }
  243 +
  244 + private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg) {
  245 + return new PluginCallback<List<AttributeKvEntry>>() {
  246 + @Override
  247 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) {
  248 + List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),
  249 + attribute.getKey(), attribute.getValue())).collect(Collectors.toList());
  250 + msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK));
  251 + }
  252 +
  253 + @Override
  254 + public void onFailure(PluginContext ctx, Exception e) {
  255 + log.error("Failed to fetch attributes", e);
  256 + msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  257 + }
  258 + };
208 } 259 }
209 } 260 }
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.extensions.core.plugin.telemetry.handlers; 16 package org.thingsboard.server.extensions.core.plugin.telemetry.handlers;
17 17
  18 +import lombok.extern.slf4j.Slf4j;
18 import org.thingsboard.server.common.data.DataConstants; 19 import org.thingsboard.server.common.data.DataConstants;
19 import org.thingsboard.server.common.data.id.DeviceId; 20 import org.thingsboard.server.common.data.id.DeviceId;
20 import org.thingsboard.server.common.data.id.RuleId; 21 import org.thingsboard.server.common.data.id.RuleId;
@@ -38,6 +39,7 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT @@ -38,6 +39,7 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT
38 import java.util.*; 39 import java.util.*;
39 import java.util.stream.Collectors; 40 import java.util.stream.Collectors;
40 41
  42 +@Slf4j
41 public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler { 43 public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler {
42 private final SubscriptionManager subscriptionManager; 44 private final SubscriptionManager subscriptionManager;
43 45
@@ -49,27 +51,36 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler { @@ -49,27 +51,36 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler {
49 public void handleGetAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, GetAttributesRequestRuleToPluginMsg msg) { 51 public void handleGetAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, GetAttributesRequestRuleToPluginMsg msg) {
50 GetAttributesRequest request = msg.getPayload(); 52 GetAttributesRequest request = msg.getPayload();
51 53
52 - List<AttributeKvEntry> clientAttributes = getAttributeKvEntries(ctx, msg.getDeviceId(), DataConstants.CLIENT_SCOPE, request.getClientAttributeNames());  
53 - List<AttributeKvEntry> sharedAttributes = getAttributeKvEntries(ctx, msg.getDeviceId(), DataConstants.SHARED_SCOPE, request.getSharedAttributeNames()); 54 + BiPluginCallBack<List<AttributeKvEntry>, List<AttributeKvEntry>> callback = new BiPluginCallBack<List<AttributeKvEntry>, List<AttributeKvEntry>>() {
54 55
55 - BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(),  
56 - request.getRequestId(), BasicAttributeKVMsg.from(clientAttributes, sharedAttributes)); 56 + @Override
  57 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> clientAttributes, List<AttributeKvEntry> sharedAttributes) {
  58 + BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(),
  59 + request.getRequestId(), BasicAttributeKVMsg.from(clientAttributes, sharedAttributes));
  60 + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, response));
  61 + }
  62 +
  63 + @Override
  64 + public void onFailure(PluginContext ctx, Exception e) {
  65 + log.error("Failed to process get attributes request", e);
  66 + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, BasicStatusCodeResponse.onError(request.getMsgType(), request.getRequestId(), e)));
  67 + }
  68 + };
57 69
58 - ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, response)); 70 + getAttributeKvEntries(ctx, msg.getDeviceId(), DataConstants.CLIENT_SCOPE, request.getClientAttributeNames(), callback.getV1Callback());
  71 + getAttributeKvEntries(ctx, msg.getDeviceId(), DataConstants.SHARED_SCOPE, request.getSharedAttributeNames(), callback.getV2Callback());
59 } 72 }
60 73
61 - private List<AttributeKvEntry> getAttributeKvEntries(PluginContext ctx, DeviceId deviceId, String scope, Optional<Set<String>> names) {  
62 - List<AttributeKvEntry> attributes; 74 + private void getAttributeKvEntries(PluginContext ctx, DeviceId deviceId, String scope, Optional<Set<String>> names, PluginCallback<List<AttributeKvEntry>> callback) {
63 if (names.isPresent()) { 75 if (names.isPresent()) {
64 if (!names.get().isEmpty()) { 76 if (!names.get().isEmpty()) {
65 - attributes = ctx.loadAttributes(deviceId, scope, new ArrayList<>(names.get())); 77 + ctx.loadAttributes(deviceId, scope, new ArrayList<>(names.get()), callback);
66 } else { 78 } else {
67 - attributes = ctx.loadAttributes(deviceId, scope); 79 + ctx.loadAttributes(deviceId, scope, callback);
68 } 80 }
69 } else { 81 } else {
70 - attributes = Collections.emptyList(); 82 + callback.onSuccess(ctx, Collections.emptyList());
71 } 83 }
72 - return attributes;  
73 } 84 }
74 85
75 @Override 86 @Override
@@ -100,6 +111,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler { @@ -100,6 +111,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler {
100 111
101 @Override 112 @Override
102 public void onFailure(PluginContext ctx, Exception e) { 113 public void onFailure(PluginContext ctx, Exception e) {
  114 + log.error("Failed to process telemetry upload request", e);
103 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, BasicStatusCodeResponse.onError(request.getMsgType(), request.getRequestId(), e))); 115 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, BasicStatusCodeResponse.onError(request.getMsgType(), request.getRequestId(), e)));
104 } 116 }
105 }); 117 });
@@ -127,6 +139,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler { @@ -127,6 +139,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler {
127 139
128 @Override 140 @Override
129 public void onFailure(PluginContext ctx, Exception e) { 141 public void onFailure(PluginContext ctx, Exception e) {
  142 + log.error("Failed to process attributes update request", e);
130 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, BasicStatusCodeResponse.onError(request.getMsgType(), request.getRequestId(), e))); 143 ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, BasicStatusCodeResponse.onError(request.getMsgType(), request.getRequestId(), e)));
131 } 144 }
132 }); 145 });
@@ -104,37 +104,64 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { @@ -104,37 +104,64 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
104 SubscriptionState sub; 104 SubscriptionState sub;
105 if (keysOptional.isPresent()) { 105 if (keysOptional.isPresent()) {
106 List<String> keys = new ArrayList<>(keysOptional.get()); 106 List<String> keys = new ArrayList<>(keysOptional.get());
107 - List<AttributeKvEntry> data = new ArrayList<>(); 107 +
  108 + PluginCallback<List<AttributeKvEntry>> callback = new PluginCallback<List<AttributeKvEntry>>() {
  109 + @Override
  110 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> data) {
  111 + List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
  112 + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
  113 +
  114 + Map<String, Long> subState = new HashMap<>(keys.size());
  115 + keys.forEach(key -> subState.put(key, 0L));
  116 + attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
  117 +
  118 + SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState);
  119 + subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
  120 + }
  121 +
  122 + @Override
  123 + public void onFailure(PluginContext ctx, Exception e) {
  124 + log.error("Failed to fetch attributes!", e);
  125 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  126 + "Failed to fetch attributes!");
  127 + sendWsMsg(ctx, sessionRef, update);
  128 + }
  129 + };
  130 +
108 if (StringUtils.isEmpty(cmd.getScope())) { 131 if (StringUtils.isEmpty(cmd.getScope())) {
109 - Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> data.addAll(ctx.loadAttributes(deviceId, s, keys))); 132 + ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), keys, callback);
110 } else { 133 } else {
111 - data.addAll(ctx.loadAttributes(deviceId, cmd.getScope(), keys)); 134 + ctx.loadAttributes(deviceId, cmd.getScope(), keys, callback);
112 } 135 }
  136 + } else {
  137 + PluginCallback<List<AttributeKvEntry>> callback = new PluginCallback<List<AttributeKvEntry>>() {
  138 + @Override
  139 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> data) {
  140 + List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
  141 + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
113 142
114 - List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());  
115 - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData)); 143 + Map<String, Long> subState = new HashMap<>(attributesData.size());
  144 + attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
116 145
117 - Map<String, Long> subState = new HashMap<>(keys.size());  
118 - keys.forEach(key -> subState.put(key, 0L));  
119 - attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); 146 + SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, true, subState);
  147 + subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
  148 + }
  149 +
  150 + @Override
  151 + public void onFailure(PluginContext ctx, Exception e) {
  152 + log.error("Failed to fetch attributes!", e);
  153 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  154 + "Failed to fetch attributes!");
  155 + sendWsMsg(ctx, sessionRef, update);
  156 + }
  157 + };
120 158
121 - sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, false, subState);  
122 - } else {  
123 - List<AttributeKvEntry> data = new ArrayList<>();  
124 if (StringUtils.isEmpty(cmd.getScope())) { 159 if (StringUtils.isEmpty(cmd.getScope())) {
125 - Arrays.stream(DataConstants.ALL_SCOPES).forEach(s -> data.addAll(ctx.loadAttributes(deviceId, s))); 160 + ctx.loadAttributes(deviceId, Arrays.asList(DataConstants.ALL_SCOPES), callback);
126 } else { 161 } else {
127 - data.addAll(ctx.loadAttributes(deviceId, cmd.getScope())); 162 + ctx.loadAttributes(deviceId, cmd.getScope(), callback);
128 } 163 }
129 - List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());  
130 - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));  
131 -  
132 - Map<String, Long> subState = new HashMap<>(attributesData.size());  
133 - attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));  
134 -  
135 - sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.ATTRIBUTES, true, subState);  
136 } 164 }
137 - subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);  
138 } 165 }
139 } 166 }
140 } 167 }
@@ -205,6 +232,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler { @@ -205,6 +232,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
205 232
206 @Override 233 @Override
207 public void onFailure(PluginContext ctx, Exception e) { 234 public void onFailure(PluginContext ctx, Exception e) {
  235 + log.error("Failed to fetch data!", e);
208 SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, 236 SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
209 "Failed to fetch data!"); 237 "Failed to fetch data!");
210 sendWsMsg(ctx, sessionRef, update); 238 sendWsMsg(ctx, sessionRef, update);