Commit 5aa055d73d338931a6dd014a6c3a6d67c5c94258

Authored by Andrii Shvaika
1 parent 4899c3c3

Websocket refactoring

... ... @@ -304,6 +304,11 @@
304 304 <groupId>com.github.ua-parser</groupId>
305 305 <artifactId>uap-java</artifactId>
306 306 </dependency>
  307 + <dependency>
  308 + <groupId>org.java-websocket</groupId>
  309 + <artifactId>Java-WebSocket</artifactId>
  310 + <scope>test</scope>
  311 + </dependency>
307 312 </dependencies>
308 313
309 314 <build>
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.subscription;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.context.annotation.Lazy;
  23 +import org.springframework.context.event.EventListener;
  24 +import org.springframework.stereotype.Service;
  25 +import org.thingsboard.common.util.ThingsBoardThreadFactory;
  26 +import org.thingsboard.server.common.data.EntityView;
  27 +import org.thingsboard.server.common.data.id.CustomerId;
  28 +import org.thingsboard.server.common.data.id.EntityViewId;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
  31 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
  32 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  33 +import org.thingsboard.server.common.data.page.PageData;
  34 +import org.thingsboard.server.common.data.query.EntityData;
  35 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  36 +import org.thingsboard.server.common.data.query.TsValue;
  37 +import org.thingsboard.server.common.msg.queue.ServiceType;
  38 +import org.thingsboard.server.common.msg.queue.TbCallback;
  39 +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
  40 +import org.thingsboard.server.dao.entity.EntityService;
  41 +import org.thingsboard.server.dao.entityview.EntityViewService;
  42 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  43 +import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent;
  44 +import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  45 +import org.thingsboard.server.queue.discovery.PartitionService;
  46 +import org.thingsboard.server.queue.util.TbCoreComponent;
  47 +import org.thingsboard.server.service.queue.TbClusterService;
  48 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
  49 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  50 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  51 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
  52 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  53 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
  54 +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
  55 +import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
  56 +import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate;
  57 +
  58 +import javax.annotation.PostConstruct;
  59 +import javax.annotation.PreDestroy;
  60 +import java.util.ArrayList;
  61 +import java.util.HashMap;
  62 +import java.util.LinkedHashMap;
  63 +import java.util.List;
  64 +import java.util.Map;
  65 +import java.util.Set;
  66 +import java.util.concurrent.ConcurrentHashMap;
  67 +import java.util.concurrent.ExecutionException;
  68 +import java.util.concurrent.ExecutorService;
  69 +import java.util.concurrent.Executors;
  70 +import java.util.function.Function;
  71 +import java.util.stream.Collectors;
  72 +
  73 +@Slf4j
  74 +@TbCoreComponent
  75 +@Service
  76 +public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
  77 +
  78 + private static final int DEFAULT_LIMIT = 100;
  79 + private final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet();
  80 + private final Map<String, Map<Integer, TbSubscription>> subscriptionsBySessionId = new ConcurrentHashMap<>();
  81 +
  82 + @Autowired
  83 + private TelemetryWebSocketService wsService;
  84 +
  85 + @Autowired
  86 + private EntityViewService entityViewService;
  87 +
  88 + @Autowired
  89 + private EntityService entityService;
  90 +
  91 + @Autowired
  92 + private PartitionService partitionService;
  93 +
  94 + @Autowired
  95 + private TbClusterService clusterService;
  96 +
  97 + @Autowired
  98 + @Lazy
  99 + private SubscriptionManagerService subscriptionManagerService;
  100 +
  101 + @Autowired
  102 + private TimeseriesService tsService;
  103 +
  104 + private ExecutorService wsCallBackExecutor;
  105 +
  106 + @PostConstruct
  107 + public void initExecutor() {
  108 + wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-entity-sub-callback"));
  109 + }
  110 +
  111 + @PreDestroy
  112 + public void shutdownExecutor() {
  113 + if (wsCallBackExecutor != null) {
  114 + wsCallBackExecutor.shutdownNow();
  115 + }
  116 + }
  117 +
  118 + @Override
  119 + @EventListener(PartitionChangeEvent.class)
  120 + public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
  121 + if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
  122 + currentPartitions.clear();
  123 + currentPartitions.addAll(partitionChangeEvent.getPartitions());
  124 + }
  125 + }
  126 +
  127 + @Override
  128 + @EventListener(ClusterTopologyChangeEvent.class)
  129 + public void onApplicationEvent(ClusterTopologyChangeEvent event) {
  130 + if (event.getServiceQueueKeys().stream().anyMatch(key -> ServiceType.TB_CORE.equals(key.getServiceType()))) {
  131 + /*
  132 + * If the cluster topology has changed, we need to push all current subscriptions to SubscriptionManagerService again.
  133 + * Otherwise, the SubscriptionManagerService may "forget" those subscriptions in case of restart.
  134 + * Although this is resource consuming operation, it is cheaper than sending ping/pong commands periodically
  135 + * It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService.
  136 + * Even if we cache locally the list of active subscriptions by entity id, it is still time consuming operation to get them from cache
  137 + * Since number of subscriptions is usually much less then number of devices that are pushing data.
  138 +// */
  139 +// subscriptionsBySessionId.values().forEach(map -> map.values()
  140 +// .forEach(sub -> pushSubscriptionToManagerService(sub, false)));
  141 + }
  142 + }
  143 +
  144 + @Override
  145 + public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) {
  146 + if (cmd.getHistoryCmd() != null) {
  147 + handleHistoryCmd(session, cmd.getCmdId(), cmd.getQuery(), cmd.getHistoryCmd());
  148 + } else if (cmd.getLatestCmd() != null) {
  149 + handleLatestCmd(session, cmd.getCmdId(), cmd.getQuery(), cmd.getLatestCmd());
  150 + } else {
  151 + handleTimeseriesCmd(session, cmd.getCmdId(), cmd.getQuery(), cmd.getTsCmd());
  152 + }
  153 + }
  154 +
  155 + private void handleTimeseriesCmd(TelemetryWebSocketSessionRef session, int cmdId, EntityDataQuery query, TimeSeriesCmd tsCmd) {
  156 + }
  157 +
  158 + private void handleLatestCmd(TelemetryWebSocketSessionRef session, int cmdId, EntityDataQuery query, LatestValueCmd latestCmd) {
  159 +
  160 + }
  161 +
  162 + private void handleHistoryCmd(TelemetryWebSocketSessionRef session, int cmdId, EntityDataQuery query, EntityHistoryCmd historyCmd) {
  163 + TenantId tenantId = session.getSecurityCtx().getTenantId();
  164 + CustomerId customerId = session.getSecurityCtx().getCustomerId();
  165 + PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, customerId, query);
  166 + List<ReadTsKvQuery> tsKvQueryList = historyCmd.getKeys().stream().map(key -> new BaseReadTsKvQuery(
  167 + key, historyCmd.getStartTs(), historyCmd.getEndTs(), historyCmd.getInterval(), getLimit(historyCmd.getLimit()), historyCmd.getAgg()
  168 + )).collect(Collectors.toList());
  169 + Map<EntityData, ListenableFuture<List<TsKvEntry>>> fetchResultMap = new HashMap<>();
  170 + data.getData().forEach(entityData -> fetchResultMap.put(entityData,
  171 + tsService.findAll(tenantId, entityData.getEntityId(), tsKvQueryList)));
  172 + Futures.allAsList(fetchResultMap.values()).addListener(() -> {
  173 + fetchResultMap.forEach((entityData, future) -> {
  174 + Map<String, List<TsValue>> keyData = new LinkedHashMap<>();
  175 + historyCmd.getKeys().forEach(key -> keyData.put(key, new ArrayList<>()));
  176 + try {
  177 + List<TsKvEntry> entityTsData = future.get();
  178 + if (entityTsData != null) {
  179 + entityTsData.forEach(entry -> keyData.get(entry.getKey()).add(new TsValue(entry.getTs(), entry.getValueAsString())));
  180 + }
  181 + keyData.forEach((k, v) -> entityData.getTimeseries().put(k, v.toArray(new TsValue[v.size()])));
  182 + } catch (InterruptedException | ExecutionException e) {
  183 + log.warn("[{}][{}][{}] Failed to fetch historical data", session.getSessionId(), cmdId, entityData.getEntityId(), e);
  184 + }
  185 + });
  186 + EntityDataUpdate update = new EntityDataUpdate(cmdId, data, null);
  187 + wsService.sendWsMsg(session.getSessionId(), update);
  188 + }, wsCallBackExecutor);
  189 + }
  190 +
  191 +
  192 + @Override
  193 + public void cancelSubscription(String sessionId, EntityDataUnsubscribeCmd subscriptionId) {
  194 +
  195 + }
  196 +
  197 +// //TODO 3.1: replace null callbacks with callbacks from websocket service.
  198 +// @Override
  199 +// public void addSubscription(TbSubscription subscription) {
  200 +// EntityId entityId = subscription.getEntityId();
  201 +// // Telemetry subscription on Entity Views are handled differently, because we need to allow only certain keys and time ranges;
  202 +// if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TbSubscriptionType.TIMESERIES.equals(subscription.getType())) {
  203 +// subscription = resolveEntityViewSubscription((TbTimeseriesSubscription) subscription);
  204 +// }
  205 +// pushSubscriptionToManagerService(subscription, true);
  206 +// registerSubscription(subscription);
  207 +// }
  208 +
  209 +// private void pushSubscriptionToManagerService(TbSubscription subscription, boolean pushToLocalService) {
  210 +// TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId());
  211 +// if (currentPartitions.contains(tpi)) {
  212 +// // Subscription is managed on the same server;
  213 +// if (pushToLocalService) {
  214 +// subscriptionManagerService.addSubscription(subscription, TbCallback.EMPTY);
  215 +// }
  216 +// } else {
  217 +// // Push to the queue;
  218 +// TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toNewSubscriptionProto(subscription);
  219 +// clusterService.pushMsgToCore(tpi, subscription.getEntityId().getId(), toCoreMsg, null);
  220 +// }
  221 +// }
  222 +
  223 + @Override
  224 + public void onSubscriptionUpdate(String sessionId, SubscriptionUpdate update, TbCallback callback) {
  225 +// TbSubscription subscription = subscriptionsBySessionId
  226 +// .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
  227 +// if (subscription != null) {
  228 +// switch (subscription.getType()) {
  229 +// case TIMESERIES:
  230 +// TbTimeseriesSubscription tsSub = (TbTimeseriesSubscription) subscription;
  231 +// update.getLatestValues().forEach((key, value) -> tsSub.getKeyStates().put(key, value));
  232 +// break;
  233 +// case ATTRIBUTES:
  234 +// TbAttributeSubscription attrSub = (TbAttributeSubscription) subscription;
  235 +// update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value));
  236 +// break;
  237 +// }
  238 +// wsService.sendWsMsg(sessionId, update);
  239 +// }
  240 +// callback.onSuccess();
  241 + }
  242 +
  243 +// @Override
  244 +// public void cancelSubscription(String sessionId, int subscriptionId) {
  245 +// log.debug("[{}][{}] Going to remove subscription.", sessionId, subscriptionId);
  246 +// Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsBySessionId.get(sessionId);
  247 +// if (sessionSubscriptions != null) {
  248 +// TbSubscription subscription = sessionSubscriptions.remove(subscriptionId);
  249 +// if (subscription != null) {
  250 +// if (sessionSubscriptions.isEmpty()) {
  251 +// subscriptionsBySessionId.remove(sessionId);
  252 +// }
  253 +// TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId());
  254 +// if (currentPartitions.contains(tpi)) {
  255 +// // Subscription is managed on the same server;
  256 +// subscriptionManagerService.cancelSubscription(sessionId, subscriptionId, TbCallback.EMPTY);
  257 +// } else {
  258 +// // Push to the queue;
  259 +// TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toCloseSubscriptionProto(subscription);
  260 +// clusterService.pushMsgToCore(tpi, subscription.getEntityId().getId(), toCoreMsg, null);
  261 +// }
  262 +// } else {
  263 +// log.debug("[{}][{}] Subscription not found!", sessionId, subscriptionId);
  264 +// }
  265 +// } else {
  266 +// log.debug("[{}] No session subscriptions found!", sessionId);
  267 +// }
  268 +// }
  269 +
  270 + @Override
  271 + public void cancelAllSessionSubscriptions(String sessionId) {
  272 +// Map<Integer, TbSubscription> subscriptions = subscriptionsBySessionId.get(sessionId);
  273 +// if (subscriptions != null) {
  274 +// Set<Integer> toRemove = new HashSet<>(subscriptions.keySet());
  275 +// toRemove.forEach(id -> cancelSubscription(sessionId, id));
  276 +// }
  277 + }
  278 +
  279 + private TbSubscription resolveEntityViewSubscription(TbTimeseriesSubscription subscription) {
  280 + EntityView entityView = entityViewService.findEntityViewById(TenantId.SYS_TENANT_ID, new EntityViewId(subscription.getEntityId().getId()));
  281 +
  282 + Map<String, Long> keyStates;
  283 + if (subscription.isAllKeys()) {
  284 + keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L));
  285 + } else {
  286 + keyStates = subscription.getKeyStates().entrySet()
  287 + .stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey()))
  288 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  289 + }
  290 +
  291 + return TbTimeseriesSubscription.builder()
  292 + .serviceId(subscription.getServiceId())
  293 + .sessionId(subscription.getSessionId())
  294 + .subscriptionId(subscription.getSubscriptionId())
  295 + .tenantId(subscription.getTenantId())
  296 + .entityId(entityView.getEntityId())
  297 + .startTime(entityView.getStartTimeMs())
  298 + .endTime(entityView.getEndTimeMs())
  299 + .allKeys(false)
  300 + .keyStates(keyStates).build();
  301 + }
  302 +
  303 + private void registerSubscription(TbSubscription subscription) {
  304 + Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsBySessionId.computeIfAbsent(subscription.getSessionId(), k -> new ConcurrentHashMap<>());
  305 + sessionSubscriptions.put(subscription.getSubscriptionId(), subscription);
  306 + }
  307 +
  308 + private int getLimit(int limit) {
  309 + return limit == 0 ? DEFAULT_LIMIT : limit;
  310 + }
  311 +
  312 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.subscription;
  17 +
  18 +import org.thingsboard.server.common.msg.queue.TbCallback;
  19 +import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent;
  20 +import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  21 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  22 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  23 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
  24 +import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate;
  25 +
  26 +public interface TbEntityDataSubscriptionService {
  27 +
  28 + void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
  29 +
  30 + void cancelSubscription(String sessionId, EntityDataUnsubscribeCmd subscriptionId);
  31 +
  32 + void cancelAllSessionSubscriptions(String sessionId);
  33 +
  34 + void onSubscriptionUpdate(String sessionId, SubscriptionUpdate update, TbCallback callback);
  35 +
  36 + void onApplicationEvent(PartitionChangeEvent event);
  37 +
  38 + void onApplicationEvent(ClusterTopologyChangeEvent event);
  39 +}
... ...
... ... @@ -49,8 +49,10 @@ import org.thingsboard.server.service.security.AccessValidator;
49 49 import org.thingsboard.server.service.security.ValidationCallback;
50 50 import org.thingsboard.server.service.security.ValidationResult;
51 51 import org.thingsboard.server.service.security.ValidationResultCode;
  52 +import org.thingsboard.server.service.security.model.SecurityUser;
52 53 import org.thingsboard.server.service.security.model.UserPrincipal;
53 54 import org.thingsboard.server.service.security.permission.Operation;
  55 +import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService;
54 56 import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
55 57 import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
56 58 import org.thingsboard.server.service.subscription.TbAttributeSubscription;
... ... @@ -61,6 +63,9 @@ import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd;
61 63 import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd;
62 64 import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
63 65 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
  66 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  67 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
  68 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
64 69 import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
65 70 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
66 71 import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate;
... ... @@ -104,7 +109,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
104 109 private final ConcurrentMap<String, WsSessionMetaData> wsSessionsMap = new ConcurrentHashMap<>();
105 110
106 111 @Autowired
107   - private TbLocalSubscriptionService subService;
  112 + private TbLocalSubscriptionService oldSubService;
  113 +
  114 + @Autowired
  115 + private TbEntityDataSubscriptionService entityDataSubService;
108 116
109 117 @Autowired
110 118 private TelemetryWebSocketMsgEndpoint msgEndpoint;
... ... @@ -164,7 +172,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
164 172 break;
165 173 case CLOSED:
166 174 wsSessionsMap.remove(sessionId);
167   - subService.cancelAllSessionSubscriptions(sessionId);
  175 + oldSubService.cancelAllSessionSubscriptions(sessionId);
  176 + entityDataSubService.cancelAllSessionSubscriptions(sessionId);
168 177 processSessionClose(sessionRef);
169 178 break;
170 179 }
... ... @@ -196,6 +205,12 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
196 205 if (cmdsWrapper.getHistoryCmds() != null) {
197 206 cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd));
198 207 }
  208 + if (cmdsWrapper.getEntityDataCmds() != null) {
  209 + cmdsWrapper.getEntityDataCmds().forEach(cmd -> handleWsEntityDataCmd(sessionRef, cmd));
  210 + }
  211 + if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
  212 + cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsEntityDataUnsubscribeCmd(sessionRef, cmd));
  213 + }
199 214 }
200 215 } catch (IOException e) {
201 216 log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
... ... @@ -204,11 +219,39 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
204 219 }
205 220 }
206 221
  222 + private void handleWsEntityDataCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
  223 + String sessionId = sessionRef.getSessionId();
  224 + log.debug("[{}] Processing: {}", sessionId, cmd);
  225 +
  226 + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
  227 + && validateSubscriptionCmd(sessionRef, cmd)) {
  228 + entityDataSubService.handleCmd(sessionRef, cmd);
  229 + }
  230 + }
  231 +
  232 + private void handleWsEntityDataUnsubscribeCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataUnsubscribeCmd cmd) {
  233 + String sessionId = sessionRef.getSessionId();
  234 + log.debug("[{}] Processing: {}", sessionId, cmd);
  235 +
  236 + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) {
  237 + entityDataSubService.cancelSubscription(sessionRef.getSessionId(), cmd);
  238 + }
  239 + }
  240 +
207 241 @Override
208 242 public void sendWsMsg(String sessionId, SubscriptionUpdate update) {
  243 + sendWsMsg(sessionId, update.getSubscriptionId(), update);
  244 + }
  245 +
  246 + @Override
  247 + public void sendWsMsg(String sessionId, EntityDataUpdate update) {
  248 + sendWsMsg(sessionId, update.getCmdId(), update);
  249 + }
  250 +
  251 + private <T> void sendWsMsg(String sessionId, int cmdId, T update) {
209 252 WsSessionMetaData md = wsSessionsMap.get(sessionId);
210 253 if (md != null) {
211   - sendWsMsg(md.getSessionRef(), update);
  254 + sendWsMsg(md.getSessionRef(), cmdId, update);
212 255 }
213 256 }
214 257
... ... @@ -356,7 +399,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
356 399 .allKeys(false)
357 400 .keyStates(subState)
358 401 .scope(scope).build();
359   - subService.addSubscription(sub);
  402 + oldSubService.addSubscription(sub);
360 403 }
361 404
362 405 @Override
... ... @@ -453,7 +496,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
453 496 .allKeys(true)
454 497 .keyStates(subState)
455 498 .scope(scope).build();
456   - subService.addSubscription(sub);
  499 + oldSubService.addSubscription(sub);
457 500 }
458 501
459 502 @Override
... ... @@ -534,7 +577,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
534 577 .entityId(entityId)
535 578 .allKeys(true)
536 579 .keyStates(subState).build();
537   - subService.addSubscription(sub);
  580 + oldSubService.addSubscription(sub);
538 581 }
539 582
540 583 @Override
... ... @@ -571,7 +614,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
571 614 .entityId(entityId)
572 615 .allKeys(false)
573 616 .keyStates(subState).build();
574   - subService.addSubscription(sub);
  617 + oldSubService.addSubscription(sub);
575 618 }
576 619
577 620 @Override
... ... @@ -590,12 +633,32 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
590 633
591 634 private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
592 635 if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
593   - subService.cancelAllSessionSubscriptions(sessionId);
  636 + oldSubService.cancelAllSessionSubscriptions(sessionId);
594 637 } else {
595   - subService.cancelSubscription(sessionId, cmd.getCmdId());
  638 + oldSubService.cancelSubscription(sessionId, cmd.getCmdId());
596 639 }
597 640 }
598 641
  642 + private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
  643 + if (cmd.getCmdId() < 0) {
  644 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
  645 + "Cmd id is negative value!");
  646 + sendWsMsg(sessionRef, update);
  647 + return false;
  648 + } else if (cmd.getQuery() == null) {
  649 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
  650 + "Query is empty!");
  651 + sendWsMsg(sessionRef, update);
  652 + return false;
  653 + } else if (cmd.getHistoryCmd() == null && cmd.getLatestCmd() == null && cmd.getTsCmd() == null) {
  654 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
  655 + "No history, latest or timeseries command present!");
  656 + sendWsMsg(sessionRef, update);
  657 + return false;
  658 + }
  659 + return true;
  660 + }
  661 +
599 662 private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
600 663 if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
601 664 SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
... ... @@ -607,10 +670,14 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
607 670 }
608 671
609 672 private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
  673 + return validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId);
  674 + }
  675 +
  676 + private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, int cmdId, String sessionId) {
610 677 WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
611 678 if (sessionMD == null) {
612 679 log.warn("[{}] Session meta data not found. ", sessionId);
613   - SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  680 + SubscriptionUpdate update = new SubscriptionUpdate(cmdId, SubscriptionErrorCode.INTERNAL_ERROR,
614 681 SESSION_META_DATA_NOT_FOUND);
615 682 sendWsMsg(sessionRef, update);
616 683 return false;
... ... @@ -619,10 +686,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
619 686 }
620 687 }
621 688
  689 + private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, EntityDataUpdate update) {
  690 + sendWsMsg(sessionRef, update.getCmdId(), update);
  691 + }
  692 +
622 693 private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, SubscriptionUpdate update) {
  694 + sendWsMsg(sessionRef, update.getSubscriptionId(), update);
  695 + }
  696 +
  697 + private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, int cmdId, Object update) {
623 698 executor.submit(() -> {
624 699 try {
625   - msgEndpoint.send(sessionRef, update.getSubscriptionId(), jsonMapper.writeValueAsString(update));
  700 + msgEndpoint.send(sessionRef, cmdId, jsonMapper.writeValueAsString(update));
626 701 } catch (JsonProcessingException e) {
627 702 log.warn("[{}] Failed to encode reply: {}", sessionRef.getSessionId(), update, e);
628 703 } catch (IOException e) {
... ... @@ -631,6 +706,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
631 706 });
632 707 }
633 708
  709 +
634 710 private static Optional<Set<String>> getKeys(TelemetryPluginCmd cmd) {
635 711 if (!StringUtils.isEmpty(cmd.getKeys())) {
636 712 Set<String> keys = new HashSet<>();
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry;
17 17
  18 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
18 19 import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate;
19 20
20 21 /**
... ... @@ -27,4 +28,7 @@ public interface TelemetryWebSocketService {
27 28 void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg);
28 29
29 30 void sendWsMsg(String sessionId, SubscriptionUpdate update);
  31 +
  32 + void sendWsMsg(String sessionId, EntityDataUpdate update);
  33 +
30 34 }
... ...
... ... @@ -15,10 +15,12 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
  18 +import lombok.Data;
18 19 import org.thingsboard.server.common.data.kv.Aggregation;
19 20
20 21 import java.util.List;
21 22
  23 +@Data
22 24 public class EntityHistoryCmd {
23 25
24 26 private List<String> keys;
... ...
... ... @@ -91,409 +91,8 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
91 91 @Configuration
92 92 @ComponentScan({"org.thingsboard.server"})
93 93 @WebAppConfiguration
94   -@SpringBootTest
  94 +@SpringBootTest()
95 95 @Slf4j
96   -public abstract class AbstractControllerTest {
97   -
98   - protected ObjectMapper mapper = new ObjectMapper();
99   -
100   - protected static final String TEST_TENANT_NAME = "TEST TENANT";
101   -
102   - protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
103   - private static final String SYS_ADMIN_PASSWORD = "sysadmin";
104   -
105   - protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
106   - private static final String TENANT_ADMIN_PASSWORD = "tenant";
107   -
108   - protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
109   - private static final String CUSTOMER_USER_PASSWORD = "customer";
110   -
111   - /** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
112   - * and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
113   - */
114   - private static final long DEFAULT_TIMEOUT = -1L;
115   -
116   - protected MediaType contentType = MediaType.APPLICATION_JSON;
117   -
118   - protected MockMvc mockMvc;
119   -
120   - protected String token;
121   - protected String refreshToken;
122   - protected String username;
123   -
124   - private TenantId tenantId;
125   -
126   - @SuppressWarnings("rawtypes")
127   - private HttpMessageConverter mappingJackson2HttpMessageConverter;
128   -
129   - @SuppressWarnings("rawtypes")
130   - private HttpMessageConverter stringHttpMessageConverter;
131   -
132   - @Autowired
133   - private WebApplicationContext webApplicationContext;
134   -
135   - @Rule
136   - public TestRule watcher = new TestWatcher() {
137   - protected void starting(Description description) {
138   - log.info("Starting test: {}", description.getMethodName());
139   - }
140   -
141   - protected void finished(Description description) {
142   - log.info("Finished test: {}", description.getMethodName());
143   - }
144   - };
145   -
146   - @Autowired
147   - void setConverters(HttpMessageConverter<?>[] converters) {
148   -
149   - this.mappingJackson2HttpMessageConverter = Arrays.stream(converters)
150   - .filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
151   - .findAny()
152   - .get();
153   -
154   - this.stringHttpMessageConverter = Arrays.stream(converters)
155   - .filter(hmc -> hmc instanceof StringHttpMessageConverter)
156   - .findAny()
157   - .get();
158   -
159   - Assert.assertNotNull("the JSON message converter must not be null",
160   - this.mappingJackson2HttpMessageConverter);
161   - }
162   -
163   - @Before
164   - public void setup() throws Exception {
165   - log.info("Executing setup");
166   - if (this.mockMvc == null) {
167   - this.mockMvc = webAppContextSetup(webApplicationContext)
168   - .apply(springSecurity()).build();
169   - }
170   - loginSysAdmin();
171   -
172   - Tenant tenant = new Tenant();
173   - tenant.setTitle(TEST_TENANT_NAME);
174   - Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
175   - Assert.assertNotNull(savedTenant);
176   - tenantId = savedTenant.getId();
177   -
178   - User tenantAdmin = new User();
179   - tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
180   - tenantAdmin.setTenantId(tenantId);
181   - tenantAdmin.setEmail(TENANT_ADMIN_EMAIL);
182   -
183   - createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD);
184   -
185   - Customer customer = new Customer();
186   - customer.setTitle("Customer");
187   - customer.setTenantId(tenantId);
188   - Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
189   -
190   - User customerUser = new User();
191   - customerUser.setAuthority(Authority.CUSTOMER_USER);
192   - customerUser.setTenantId(tenantId);
193   - customerUser.setCustomerId(savedCustomer.getId());
194   - customerUser.setEmail(CUSTOMER_USER_EMAIL);
195   -
196   - createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD);
197   -
198   - logout();
199   -
200   - log.info("Executed setup");
201   - }
202   -
203   - @After
204   - public void teardown() throws Exception {
205   - log.info("Executing teardown");
206   - loginSysAdmin();
207   - doDelete("/api/tenant/" + tenantId.getId().toString())
208   - .andExpect(status().isOk());
209   - log.info("Executed teardown");
210   - }
211   -
212   - protected void loginSysAdmin() throws Exception {
213   - login(SYS_ADMIN_EMAIL, SYS_ADMIN_PASSWORD);
214   - }
215   -
216   - protected void loginTenantAdmin() throws Exception {
217   - login(TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD);
218   - }
219   -
220   - protected void loginCustomerUser() throws Exception {
221   - login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
222   - }
223   -
224   - private Tenant savedDifferentTenant;
225   - protected void loginDifferentTenant() throws Exception {
226   - loginSysAdmin();
227   - Tenant tenant = new Tenant();
228   - tenant.setTitle("Different tenant");
229   - savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
230   - Assert.assertNotNull(savedDifferentTenant);
231   - User differentTenantAdmin = new User();
232   - differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
233   - differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
234   - differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
235   -
236   - createUserAndLogin(differentTenantAdmin, "testPassword");
237   - }
238   -
239   - protected void deleteDifferentTenant() throws Exception {
240   - loginSysAdmin();
241   - doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
242   - .andExpect(status().isOk());
243   - }
244   -
245   - protected User createUserAndLogin(User user, String password) throws Exception {
246   - User savedUser = doPost("/api/user", user, User.class);
247   - logout();
248   - doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
249   - .andExpect(status().isSeeOther())
250   - .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
251   - JsonNode activateRequest = new ObjectMapper().createObjectNode()
252   - .put("activateToken", TestMailService.currentActivateToken)
253   - .put("password", password);
254   - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
255   - validateAndSetJwtToken(tokenInfo, user.getEmail());
256   - return savedUser;
257   - }
258   -
259   - protected void login(String username, String password) throws Exception {
260   - this.token = null;
261   - this.refreshToken = null;
262   - this.username = null;
263   - JsonNode tokenInfo = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class);
264   - validateAndSetJwtToken(tokenInfo, username);
265   - }
266   -
267   - protected void refreshToken() throws Exception {
268   - this.token = null;
269   - JsonNode tokenInfo = readResponse(doPost("/api/auth/token", new RefreshTokenRequest(this.refreshToken)).andExpect(status().isOk()), JsonNode.class);
270   - validateAndSetJwtToken(tokenInfo, this.username);
271   - }
272   -
273   - protected void validateAndSetJwtToken(JsonNode tokenInfo, String username) {
274   - Assert.assertNotNull(tokenInfo);
275   - Assert.assertTrue(tokenInfo.has("token"));
276   - Assert.assertTrue(tokenInfo.has("refreshToken"));
277   - String token = tokenInfo.get("token").asText();
278   - String refreshToken = tokenInfo.get("refreshToken").asText();
279   - validateJwtToken(token, username);
280   - validateJwtToken(refreshToken, username);
281   - this.token = token;
282   - this.refreshToken = refreshToken;
283   - this.username = username;
284   - }
285   -
286   - protected void validateJwtToken(String token, String username) {
287   - Assert.assertNotNull(token);
288   - Assert.assertFalse(token.isEmpty());
289   - int i = token.lastIndexOf('.');
290   - Assert.assertTrue(i > 0);
291   - String withoutSignature = token.substring(0, i + 1);
292   - Jwt<Header, Claims> jwsClaims = Jwts.parser().parseClaimsJwt(withoutSignature);
293   - Claims claims = jwsClaims.getBody();
294   - String subject = claims.getSubject();
295   - Assert.assertEquals(username, subject);
296   - }
297   -
298   - protected void logout() throws Exception {
299   - this.token = null;
300   - this.refreshToken = null;
301   - this.username = null;
302   - }
303   -
304   - protected void setJwtToken(MockHttpServletRequestBuilder request) {
305   - if (this.token != null) {
306   - request.header(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM, "Bearer " + this.token);
307   - }
308   - }
309   -
310   - protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception {
311   - MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables);
312   - setJwtToken(getRequest);
313   - return mockMvc.perform(getRequest);
314   - }
315   -
316   - protected <T> T doGet(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception {
317   - return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
318   - }
319   -
320   - protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception {
321   - return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
322   - }
323   -
324   - protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception {
325   - MockHttpServletRequestBuilder getRequest;
326   - getRequest = get(urlTemplate, urlVariables);
327   - setJwtToken(getRequest);
328   - return mockMvc.perform(asyncDispatch(mockMvc.perform(getRequest).andExpect(request().asyncStarted()).andReturn()));
329   - }
330   -
331   - protected <T> T doGetTyped(String urlTemplate, TypeReference<T> responseType, Object... urlVariables) throws Exception {
332   - return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseType);
333   - }
334   -
335   - protected <T> T doGetTypedWithPageLink(String urlTemplate, TypeReference<T> responseType,
336   - PageLink pageLink,
337   - Object... urlVariables) throws Exception {
338   - List<Object> pageLinkVariables = new ArrayList<>();
339   - urlTemplate += "pageSize={pageSize}&page={page}";
340   - pageLinkVariables.add(pageLink.getPageSize());
341   - pageLinkVariables.add(pageLink.getPage());
342   - if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
343   - urlTemplate += "&textSearch={textSearch}";
344   - pageLinkVariables.add(pageLink.getTextSearch());
345   - }
346   - if (pageLink.getSortOrder() != null) {
347   - urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}";
348   - pageLinkVariables.add(pageLink.getSortOrder().getProperty());
349   - pageLinkVariables.add(pageLink.getSortOrder().getDirection().name());
350   - }
351   -
352   - Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
353   - System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length);
354   - System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size());
355   -
356   - return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
357   - }
358   -
359   - protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType,
360   - TimePageLink pageLink,
361   - Object... urlVariables) throws Exception {
362   - List<Object> pageLinkVariables = new ArrayList<>();
363   - urlTemplate += "pageSize={pageSize}&page={page}";
364   - pageLinkVariables.add(pageLink.getPageSize());
365   - pageLinkVariables.add(pageLink.getPage());
366   - if (pageLink.getStartTime() != null) {
367   - urlTemplate += "&startTime={startTime}";
368   - pageLinkVariables.add(pageLink.getStartTime());
369   - }
370   - if (pageLink.getEndTime() != null) {
371   - urlTemplate += "&endTime={endTime}";
372   - pageLinkVariables.add(pageLink.getEndTime());
373   - }
374   - if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
375   - urlTemplate += "&textSearch={textSearch}";
376   - pageLinkVariables.add(pageLink.getTextSearch());
377   - }
378   - if (pageLink.getSortOrder() != null) {
379   - urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}";
380   - pageLinkVariables.add(pageLink.getSortOrder().getProperty());
381   - pageLinkVariables.add(pageLink.getSortOrder().getDirection().name());
382   - }
383   - Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
384   - System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length);
385   - System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size());
386   -
387   - return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
388   - }
389   -
390   - protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
391   - return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass);
392   - }
393   -
394   - protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
395   - return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass);
396   - }
397   -
398   - protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, String... params) throws Exception {
399   - return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass);
400   - }
401   -
402   - protected <T,R> R doPostWithResponse(String urlTemplate, T content, Class<R> responseClass, String... params) throws Exception {
403   - return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass);
404   - }
405   -
406   - protected <T,R> R doPostWithTypedResponse(String urlTemplate, T content, TypeReference<R> responseType, String... params) throws Exception {
407   - return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseType);
408   - }
409   -
410   - protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
411   - return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
412   - }
413   -
414   - protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, Long timeout, String... params) throws Exception {
415   - return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass);
416   - }
417   -
418   - protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
419   - return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass);
420   - }
421   -
422   - protected ResultActions doPost(String urlTemplate, String... params) throws Exception {
423   - MockHttpServletRequestBuilder postRequest = post(urlTemplate);
424   - setJwtToken(postRequest);
425   - populateParams(postRequest, params);
426   - return mockMvc.perform(postRequest);
427   - }
428   -
429   - protected <T> ResultActions doPost(String urlTemplate, T content, String... params) throws Exception {
430   - MockHttpServletRequestBuilder postRequest = post(urlTemplate);
431   - setJwtToken(postRequest);
432   - String json = json(content);
433   - postRequest.contentType(contentType).content(json);
434   - return mockMvc.perform(postRequest);
435   - }
436   -
437   - protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception {
438   - MockHttpServletRequestBuilder postRequest = post(urlTemplate);
439   - setJwtToken(postRequest);
440   - String json = json(content);
441   - postRequest.contentType(contentType).content(json);
442   - MvcResult result = mockMvc.perform(postRequest).andReturn();
443   - result.getAsyncResult(timeout);
444   - return mockMvc.perform(asyncDispatch(result));
445   - }
446   -
447   - protected ResultActions doDelete(String urlTemplate, String... params) throws Exception {
448   - MockHttpServletRequestBuilder deleteRequest = delete(urlTemplate);
449   - setJwtToken(deleteRequest);
450   - populateParams(deleteRequest, params);
451   - return mockMvc.perform(deleteRequest);
452   - }
453   -
454   - protected void populateParams(MockHttpServletRequestBuilder request, String... params) {
455   - if (params != null && params.length > 0) {
456   - Assert.assertEquals(0, params.length % 2);
457   - MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
458   - for (int i = 0; i < params.length; i += 2) {
459   - paramsMap.add(params[i], params[i + 1]);
460   - }
461   - request.params(paramsMap);
462   - }
463   - }
464   -
465   - @SuppressWarnings("unchecked")
466   - protected String json(Object o) throws IOException {
467   - MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
468   -
469   - HttpMessageConverter converter = o instanceof String ? stringHttpMessageConverter : mappingJackson2HttpMessageConverter;
470   - converter.write(o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
471   - return mockHttpOutputMessage.getBodyAsString();
472   - }
473   -
474   - @SuppressWarnings("unchecked")
475   - protected <T> T readResponse(ResultActions result, Class<T> responseClass) throws Exception {
476   - byte[] content = result.andReturn().getResponse().getContentAsByteArray();
477   - MockHttpInputMessage mockHttpInputMessage = new MockHttpInputMessage(content);
478   - HttpMessageConverter converter = responseClass.equals(String.class) ? stringHttpMessageConverter : mappingJackson2HttpMessageConverter;
479   - return (T) converter.read(responseClass, mockHttpInputMessage);
480   - }
481   -
482   - protected <T> T readResponse(ResultActions result, TypeReference<T> type) throws Exception {
483   - byte[] content = result.andReturn().getResponse().getContentAsByteArray();
484   - ObjectMapper mapper = new ObjectMapper();
485   - return mapper.readerFor(type).readValue(content);
486   - }
487   -
488   - public class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> {
489   - @Override
490   - public int compare(D o1, D o2) {
491   - return o1.getId().getId().compareTo(o2.getId().getId());
492   - }
493   - }
494   -
495   - protected static <T> ResultMatcher statusReason(Matcher<T> matcher) {
496   - return jsonPath("$.message", matcher);
497   - }
  96 +public abstract class AbstractControllerTest extends AbstractWebTest {
498 97
499 98 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import io.jsonwebtoken.Claims;
  22 +import io.jsonwebtoken.Header;
  23 +import io.jsonwebtoken.Jwt;
  24 +import io.jsonwebtoken.Jwts;
  25 +import lombok.extern.slf4j.Slf4j;
  26 +import org.apache.commons.lang3.StringUtils;
  27 +import org.hamcrest.Matcher;
  28 +import org.junit.After;
  29 +import org.junit.Assert;
  30 +import org.junit.Before;
  31 +import org.junit.Rule;
  32 +import org.junit.rules.TestRule;
  33 +import org.junit.rules.TestWatcher;
  34 +import org.junit.runner.Description;
  35 +import org.junit.runner.RunWith;
  36 +import org.springframework.beans.factory.annotation.Autowired;
  37 +import org.springframework.boot.test.context.SpringBootContextLoader;
  38 +import org.springframework.boot.test.context.SpringBootTest;
  39 +import org.springframework.context.annotation.ComponentScan;
  40 +import org.springframework.context.annotation.Configuration;
  41 +import org.springframework.http.HttpHeaders;
  42 +import org.springframework.http.MediaType;
  43 +import org.springframework.http.converter.HttpMessageConverter;
  44 +import org.springframework.http.converter.StringHttpMessageConverter;
  45 +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  46 +import org.springframework.mock.http.MockHttpInputMessage;
  47 +import org.springframework.mock.http.MockHttpOutputMessage;
  48 +import org.springframework.test.annotation.DirtiesContext;
  49 +import org.springframework.test.context.ActiveProfiles;
  50 +import org.springframework.test.context.ContextConfiguration;
  51 +import org.springframework.test.context.junit4.SpringRunner;
  52 +import org.springframework.test.web.servlet.MockMvc;
  53 +import org.springframework.test.web.servlet.MvcResult;
  54 +import org.springframework.test.web.servlet.ResultActions;
  55 +import org.springframework.test.web.servlet.ResultMatcher;
  56 +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
  57 +import org.springframework.util.LinkedMultiValueMap;
  58 +import org.springframework.util.MultiValueMap;
  59 +import org.springframework.web.context.WebApplicationContext;
  60 +import org.thingsboard.server.common.data.BaseData;
  61 +import org.thingsboard.server.common.data.Customer;
  62 +import org.thingsboard.server.common.data.Tenant;
  63 +import org.thingsboard.server.common.data.User;
  64 +import org.thingsboard.server.common.data.id.TenantId;
  65 +import org.thingsboard.server.common.data.id.UUIDBased;
  66 +import org.thingsboard.server.common.data.page.PageLink;
  67 +import org.thingsboard.server.common.data.page.TimePageLink;
  68 +import org.thingsboard.server.common.data.security.Authority;
  69 +import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
  70 +import org.thingsboard.server.service.mail.TestMailService;
  71 +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
  72 +import org.thingsboard.server.service.security.auth.rest.LoginRequest;
  73 +
  74 +import java.io.IOException;
  75 +import java.util.ArrayList;
  76 +import java.util.Arrays;
  77 +import java.util.Comparator;
  78 +import java.util.List;
  79 +
  80 +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
  81 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
  82 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
  83 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  84 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  85 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
  86 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  87 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
  88 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  89 +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
  90 +
  91 +@Slf4j
  92 +public abstract class AbstractWebTest {
  93 +
  94 + protected ObjectMapper mapper = new ObjectMapper();
  95 +
  96 + protected static final String TEST_TENANT_NAME = "TEST TENANT";
  97 +
  98 + protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
  99 + private static final String SYS_ADMIN_PASSWORD = "sysadmin";
  100 +
  101 + protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
  102 + private static final String TENANT_ADMIN_PASSWORD = "tenant";
  103 +
  104 + protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
  105 + private static final String CUSTOMER_USER_PASSWORD = "customer";
  106 +
  107 + /** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
  108 + * and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
  109 + */
  110 + private static final long DEFAULT_TIMEOUT = -1L;
  111 +
  112 + protected MediaType contentType = MediaType.APPLICATION_JSON;
  113 +
  114 + protected MockMvc mockMvc;
  115 +
  116 + protected String token;
  117 + protected String refreshToken;
  118 + protected String username;
  119 +
  120 + private TenantId tenantId;
  121 +
  122 + @SuppressWarnings("rawtypes")
  123 + private HttpMessageConverter mappingJackson2HttpMessageConverter;
  124 +
  125 + @SuppressWarnings("rawtypes")
  126 + private HttpMessageConverter stringHttpMessageConverter;
  127 +
  128 + @Autowired
  129 + private WebApplicationContext webApplicationContext;
  130 +
  131 + @Rule
  132 + public TestRule watcher = new TestWatcher() {
  133 + protected void starting(Description description) {
  134 + log.info("Starting test: {}", description.getMethodName());
  135 + }
  136 +
  137 + protected void finished(Description description) {
  138 + log.info("Finished test: {}", description.getMethodName());
  139 + }
  140 + };
  141 +
  142 + @Autowired
  143 + void setConverters(HttpMessageConverter<?>[] converters) {
  144 +
  145 + this.mappingJackson2HttpMessageConverter = Arrays.stream(converters)
  146 + .filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
  147 + .findAny()
  148 + .get();
  149 +
  150 + this.stringHttpMessageConverter = Arrays.stream(converters)
  151 + .filter(hmc -> hmc instanceof StringHttpMessageConverter)
  152 + .findAny()
  153 + .get();
  154 +
  155 + Assert.assertNotNull("the JSON message converter must not be null",
  156 + this.mappingJackson2HttpMessageConverter);
  157 + }
  158 +
  159 + @Before
  160 + public void setup() throws Exception {
  161 + log.info("Executing setup");
  162 + if (this.mockMvc == null) {
  163 + this.mockMvc = webAppContextSetup(webApplicationContext)
  164 + .apply(springSecurity()).build();
  165 + }
  166 + loginSysAdmin();
  167 +
  168 + Tenant tenant = new Tenant();
  169 + tenant.setTitle(TEST_TENANT_NAME);
  170 + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  171 + Assert.assertNotNull(savedTenant);
  172 + tenantId = savedTenant.getId();
  173 +
  174 + User tenantAdmin = new User();
  175 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  176 + tenantAdmin.setTenantId(tenantId);
  177 + tenantAdmin.setEmail(TENANT_ADMIN_EMAIL);
  178 +
  179 + createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD);
  180 +
  181 + Customer customer = new Customer();
  182 + customer.setTitle("Customer");
  183 + customer.setTenantId(tenantId);
  184 + Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
  185 +
  186 + User customerUser = new User();
  187 + customerUser.setAuthority(Authority.CUSTOMER_USER);
  188 + customerUser.setTenantId(tenantId);
  189 + customerUser.setCustomerId(savedCustomer.getId());
  190 + customerUser.setEmail(CUSTOMER_USER_EMAIL);
  191 +
  192 + createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD);
  193 +
  194 + logout();
  195 +
  196 + log.info("Executed setup");
  197 + }
  198 +
  199 + @After
  200 + public void teardown() throws Exception {
  201 + log.info("Executing teardown");
  202 + loginSysAdmin();
  203 + doDelete("/api/tenant/" + tenantId.getId().toString())
  204 + .andExpect(status().isOk());
  205 + log.info("Executed teardown");
  206 + }
  207 +
  208 + protected void loginSysAdmin() throws Exception {
  209 + login(SYS_ADMIN_EMAIL, SYS_ADMIN_PASSWORD);
  210 + }
  211 +
  212 + protected void loginTenantAdmin() throws Exception {
  213 + login(TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD);
  214 + }
  215 +
  216 + protected void loginCustomerUser() throws Exception {
  217 + login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
  218 + }
  219 +
  220 + private Tenant savedDifferentTenant;
  221 + protected void loginDifferentTenant() throws Exception {
  222 + loginSysAdmin();
  223 + Tenant tenant = new Tenant();
  224 + tenant.setTitle("Different tenant");
  225 + savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
  226 + Assert.assertNotNull(savedDifferentTenant);
  227 + User differentTenantAdmin = new User();
  228 + differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  229 + differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
  230 + differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
  231 +
  232 + createUserAndLogin(differentTenantAdmin, "testPassword");
  233 + }
  234 +
  235 + protected void deleteDifferentTenant() throws Exception {
  236 + loginSysAdmin();
  237 + doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
  238 + .andExpect(status().isOk());
  239 + }
  240 +
  241 + protected User createUserAndLogin(User user, String password) throws Exception {
  242 + User savedUser = doPost("/api/user", user, User.class);
  243 + logout();
  244 + doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
  245 + .andExpect(status().isSeeOther())
  246 + .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
  247 + JsonNode activateRequest = new ObjectMapper().createObjectNode()
  248 + .put("activateToken", TestMailService.currentActivateToken)
  249 + .put("password", password);
  250 + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
  251 + validateAndSetJwtToken(tokenInfo, user.getEmail());
  252 + return savedUser;
  253 + }
  254 +
  255 + protected void login(String username, String password) throws Exception {
  256 + this.token = null;
  257 + this.refreshToken = null;
  258 + this.username = null;
  259 + JsonNode tokenInfo = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class);
  260 + validateAndSetJwtToken(tokenInfo, username);
  261 + }
  262 +
  263 + protected void refreshToken() throws Exception {
  264 + this.token = null;
  265 + JsonNode tokenInfo = readResponse(doPost("/api/auth/token", new RefreshTokenRequest(this.refreshToken)).andExpect(status().isOk()), JsonNode.class);
  266 + validateAndSetJwtToken(tokenInfo, this.username);
  267 + }
  268 +
  269 + protected void validateAndSetJwtToken(JsonNode tokenInfo, String username) {
  270 + Assert.assertNotNull(tokenInfo);
  271 + Assert.assertTrue(tokenInfo.has("token"));
  272 + Assert.assertTrue(tokenInfo.has("refreshToken"));
  273 + String token = tokenInfo.get("token").asText();
  274 + String refreshToken = tokenInfo.get("refreshToken").asText();
  275 + validateJwtToken(token, username);
  276 + validateJwtToken(refreshToken, username);
  277 + this.token = token;
  278 + this.refreshToken = refreshToken;
  279 + this.username = username;
  280 + }
  281 +
  282 + protected void validateJwtToken(String token, String username) {
  283 + Assert.assertNotNull(token);
  284 + Assert.assertFalse(token.isEmpty());
  285 + int i = token.lastIndexOf('.');
  286 + Assert.assertTrue(i > 0);
  287 + String withoutSignature = token.substring(0, i + 1);
  288 + Jwt<Header, Claims> jwsClaims = Jwts.parser().parseClaimsJwt(withoutSignature);
  289 + Claims claims = jwsClaims.getBody();
  290 + String subject = claims.getSubject();
  291 + Assert.assertEquals(username, subject);
  292 + }
  293 +
  294 + protected void logout() throws Exception {
  295 + this.token = null;
  296 + this.refreshToken = null;
  297 + this.username = null;
  298 + }
  299 +
  300 + protected void setJwtToken(MockHttpServletRequestBuilder request) {
  301 + if (this.token != null) {
  302 + request.header(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM, "Bearer " + this.token);
  303 + }
  304 + }
  305 +
  306 + protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception {
  307 + MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables);
  308 + setJwtToken(getRequest);
  309 + return mockMvc.perform(getRequest);
  310 + }
  311 +
  312 + protected <T> T doGet(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception {
  313 + return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
  314 + }
  315 +
  316 + protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception {
  317 + return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass);
  318 + }
  319 +
  320 + protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception {
  321 + MockHttpServletRequestBuilder getRequest;
  322 + getRequest = get(urlTemplate, urlVariables);
  323 + setJwtToken(getRequest);
  324 + return mockMvc.perform(asyncDispatch(mockMvc.perform(getRequest).andExpect(request().asyncStarted()).andReturn()));
  325 + }
  326 +
  327 + protected <T> T doGetTyped(String urlTemplate, TypeReference<T> responseType, Object... urlVariables) throws Exception {
  328 + return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseType);
  329 + }
  330 +
  331 + protected <T> T doGetTypedWithPageLink(String urlTemplate, TypeReference<T> responseType,
  332 + PageLink pageLink,
  333 + Object... urlVariables) throws Exception {
  334 + List<Object> pageLinkVariables = new ArrayList<>();
  335 + urlTemplate += "pageSize={pageSize}&page={page}";
  336 + pageLinkVariables.add(pageLink.getPageSize());
  337 + pageLinkVariables.add(pageLink.getPage());
  338 + if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
  339 + urlTemplate += "&textSearch={textSearch}";
  340 + pageLinkVariables.add(pageLink.getTextSearch());
  341 + }
  342 + if (pageLink.getSortOrder() != null) {
  343 + urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}";
  344 + pageLinkVariables.add(pageLink.getSortOrder().getProperty());
  345 + pageLinkVariables.add(pageLink.getSortOrder().getDirection().name());
  346 + }
  347 +
  348 + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
  349 + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length);
  350 + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size());
  351 +
  352 + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
  353 + }
  354 +
  355 + protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType,
  356 + TimePageLink pageLink,
  357 + Object... urlVariables) throws Exception {
  358 + List<Object> pageLinkVariables = new ArrayList<>();
  359 + urlTemplate += "pageSize={pageSize}&page={page}";
  360 + pageLinkVariables.add(pageLink.getPageSize());
  361 + pageLinkVariables.add(pageLink.getPage());
  362 + if (pageLink.getStartTime() != null) {
  363 + urlTemplate += "&startTime={startTime}";
  364 + pageLinkVariables.add(pageLink.getStartTime());
  365 + }
  366 + if (pageLink.getEndTime() != null) {
  367 + urlTemplate += "&endTime={endTime}";
  368 + pageLinkVariables.add(pageLink.getEndTime());
  369 + }
  370 + if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
  371 + urlTemplate += "&textSearch={textSearch}";
  372 + pageLinkVariables.add(pageLink.getTextSearch());
  373 + }
  374 + if (pageLink.getSortOrder() != null) {
  375 + urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}";
  376 + pageLinkVariables.add(pageLink.getSortOrder().getProperty());
  377 + pageLinkVariables.add(pageLink.getSortOrder().getDirection().name());
  378 + }
  379 + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
  380 + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length);
  381 + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size());
  382 +
  383 + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
  384 + }
  385 +
  386 + protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
  387 + return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass);
  388 + }
  389 +
  390 + protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
  391 + return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass);
  392 + }
  393 +
  394 + protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, String... params) throws Exception {
  395 + return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass);
  396 + }
  397 +
  398 + protected <T,R> R doPostWithResponse(String urlTemplate, T content, Class<R> responseClass, String... params) throws Exception {
  399 + return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass);
  400 + }
  401 +
  402 + protected <T,R> R doPostWithTypedResponse(String urlTemplate, T content, TypeReference<R> responseType, String... params) throws Exception {
  403 + return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseType);
  404 + }
  405 +
  406 + protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
  407 + return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
  408 + }
  409 +
  410 + protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, Long timeout, String... params) throws Exception {
  411 + return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass);
  412 + }
  413 +
  414 + protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
  415 + return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass);
  416 + }
  417 +
  418 + protected ResultActions doPost(String urlTemplate, String... params) throws Exception {
  419 + MockHttpServletRequestBuilder postRequest = post(urlTemplate);
  420 + setJwtToken(postRequest);
  421 + populateParams(postRequest, params);
  422 + return mockMvc.perform(postRequest);
  423 + }
  424 +
  425 + protected <T> ResultActions doPost(String urlTemplate, T content, String... params) throws Exception {
  426 + MockHttpServletRequestBuilder postRequest = post(urlTemplate);
  427 + setJwtToken(postRequest);
  428 + String json = json(content);
  429 + postRequest.contentType(contentType).content(json);
  430 + return mockMvc.perform(postRequest);
  431 + }
  432 +
  433 + protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception {
  434 + MockHttpServletRequestBuilder postRequest = post(urlTemplate);
  435 + setJwtToken(postRequest);
  436 + String json = json(content);
  437 + postRequest.contentType(contentType).content(json);
  438 + MvcResult result = mockMvc.perform(postRequest).andReturn();
  439 + result.getAsyncResult(timeout);
  440 + return mockMvc.perform(asyncDispatch(result));
  441 + }
  442 +
  443 + protected ResultActions doDelete(String urlTemplate, String... params) throws Exception {
  444 + MockHttpServletRequestBuilder deleteRequest = delete(urlTemplate);
  445 + setJwtToken(deleteRequest);
  446 + populateParams(deleteRequest, params);
  447 + return mockMvc.perform(deleteRequest);
  448 + }
  449 +
  450 + protected void populateParams(MockHttpServletRequestBuilder request, String... params) {
  451 + if (params != null && params.length > 0) {
  452 + Assert.assertEquals(0, params.length % 2);
  453 + MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
  454 + for (int i = 0; i < params.length; i += 2) {
  455 + paramsMap.add(params[i], params[i + 1]);
  456 + }
  457 + request.params(paramsMap);
  458 + }
  459 + }
  460 +
  461 + @SuppressWarnings("unchecked")
  462 + protected String json(Object o) throws IOException {
  463 + MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
  464 +
  465 + HttpMessageConverter converter = o instanceof String ? stringHttpMessageConverter : mappingJackson2HttpMessageConverter;
  466 + converter.write(o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
  467 + return mockHttpOutputMessage.getBodyAsString();
  468 + }
  469 +
  470 + @SuppressWarnings("unchecked")
  471 + protected <T> T readResponse(ResultActions result, Class<T> responseClass) throws Exception {
  472 + byte[] content = result.andReturn().getResponse().getContentAsByteArray();
  473 + MockHttpInputMessage mockHttpInputMessage = new MockHttpInputMessage(content);
  474 + HttpMessageConverter converter = responseClass.equals(String.class) ? stringHttpMessageConverter : mappingJackson2HttpMessageConverter;
  475 + return (T) converter.read(responseClass, mockHttpInputMessage);
  476 + }
  477 +
  478 + protected <T> T readResponse(ResultActions result, TypeReference<T> type) throws Exception {
  479 + byte[] content = result.andReturn().getResponse().getContentAsByteArray();
  480 + ObjectMapper mapper = new ObjectMapper();
  481 + return mapper.readerFor(type).readValue(content);
  482 + }
  483 +
  484 + public class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> {
  485 + @Override
  486 + public int compare(D o1, D o2) {
  487 + return o1.getId().getId().compareTo(o2.getId().getId());
  488 + }
  489 + }
  490 +
  491 + protected static <T> ResultMatcher statusReason(Matcher<T> matcher) {
  492 + return jsonPath("$.message", matcher);
  493 + }
  494 +
  495 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import io.jsonwebtoken.Claims;
  22 +import io.jsonwebtoken.Header;
  23 +import io.jsonwebtoken.Jwt;
  24 +import io.jsonwebtoken.Jwts;
  25 +import lombok.extern.slf4j.Slf4j;
  26 +import org.apache.commons.lang3.StringUtils;
  27 +import org.hamcrest.Matcher;
  28 +import org.junit.After;
  29 +import org.junit.Assert;
  30 +import org.junit.Before;
  31 +import org.junit.Rule;
  32 +import org.junit.rules.TestRule;
  33 +import org.junit.rules.TestWatcher;
  34 +import org.junit.runner.Description;
  35 +import org.junit.runner.RunWith;
  36 +import org.springframework.beans.factory.annotation.Autowired;
  37 +import org.springframework.boot.test.context.SpringBootContextLoader;
  38 +import org.springframework.boot.test.context.SpringBootTest;
  39 +import org.springframework.boot.web.server.LocalServerPort;
  40 +import org.springframework.context.annotation.ComponentScan;
  41 +import org.springframework.context.annotation.Configuration;
  42 +import org.springframework.http.HttpHeaders;
  43 +import org.springframework.http.MediaType;
  44 +import org.springframework.http.converter.HttpMessageConverter;
  45 +import org.springframework.http.converter.StringHttpMessageConverter;
  46 +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  47 +import org.springframework.mock.http.MockHttpInputMessage;
  48 +import org.springframework.mock.http.MockHttpOutputMessage;
  49 +import org.springframework.test.annotation.DirtiesContext;
  50 +import org.springframework.test.context.ActiveProfiles;
  51 +import org.springframework.test.context.ContextConfiguration;
  52 +import org.springframework.test.context.junit4.SpringRunner;
  53 +import org.springframework.test.web.servlet.MockMvc;
  54 +import org.springframework.test.web.servlet.MvcResult;
  55 +import org.springframework.test.web.servlet.ResultActions;
  56 +import org.springframework.test.web.servlet.ResultMatcher;
  57 +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
  58 +import org.springframework.util.LinkedMultiValueMap;
  59 +import org.springframework.util.MultiValueMap;
  60 +import org.springframework.web.context.WebApplicationContext;
  61 +import org.thingsboard.server.common.data.BaseData;
  62 +import org.thingsboard.server.common.data.Customer;
  63 +import org.thingsboard.server.common.data.Tenant;
  64 +import org.thingsboard.server.common.data.User;
  65 +import org.thingsboard.server.common.data.id.TenantId;
  66 +import org.thingsboard.server.common.data.id.UUIDBased;
  67 +import org.thingsboard.server.common.data.page.PageLink;
  68 +import org.thingsboard.server.common.data.page.TimePageLink;
  69 +import org.thingsboard.server.common.data.security.Authority;
  70 +import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
  71 +import org.thingsboard.server.service.mail.TestMailService;
  72 +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
  73 +import org.thingsboard.server.service.security.auth.rest.LoginRequest;
  74 +
  75 +import java.io.IOException;
  76 +import java.net.URI;
  77 +import java.net.URISyntaxException;
  78 +import java.util.ArrayList;
  79 +import java.util.Arrays;
  80 +import java.util.Comparator;
  81 +import java.util.List;
  82 +
  83 +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
  84 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
  85 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
  86 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  87 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  88 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
  89 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  90 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
  91 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  92 +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
  93 +
  94 +@ActiveProfiles("test")
  95 +@RunWith(SpringRunner.class)
  96 +@ContextConfiguration(classes = AbstractControllerTest.class, loader = SpringBootContextLoader.class)
  97 +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
  98 +@Configuration
  99 +@ComponentScan({"org.thingsboard.server"})
  100 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  101 +@Slf4j
  102 +public abstract class AbstractWebsocketTest extends AbstractWebTest {
  103 +
  104 + protected static final String WS_URL = "ws://localhost:";
  105 +
  106 + @LocalServerPort
  107 + protected int wsPort;
  108 +
  109 + protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
  110 + TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token));
  111 + Assert.assertTrue(wsClient.connectBlocking());
  112 + return wsClient;
  113 + }
  114 +
  115 +}
... ...
... ... @@ -16,10 +16,16 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.fasterxml.jackson.core.type.TypeReference;
  19 +import com.google.gson.JsonArray;
  20 +import com.google.gson.JsonObject;
  21 +import org.apache.http.conn.ssl.TrustStrategy;
  22 +import org.apache.http.ssl.SSLContextBuilder;
  23 +import org.apache.http.ssl.SSLContexts;
19 24 import org.junit.After;
20 25 import org.junit.Assert;
21 26 import org.junit.Before;
22 27 import org.junit.Test;
  28 +import org.springframework.boot.web.server.LocalServerPort;
23 29 import org.thingsboard.server.common.data.DataConstants;
24 30 import org.thingsboard.server.common.data.Device;
25 31 import org.thingsboard.server.common.data.EntityType;
... ... @@ -27,6 +33,7 @@ import org.thingsboard.server.common.data.Tenant;
27 33 import org.thingsboard.server.common.data.User;
28 34 import org.thingsboard.server.common.data.id.DeviceId;
29 35 import org.thingsboard.server.common.data.id.EntityId;
  36 +import org.thingsboard.server.common.data.kv.Aggregation;
30 37 import org.thingsboard.server.common.data.page.PageData;
31 38 import org.thingsboard.server.common.data.query.DeviceTypeFilter;
32 39 import org.thingsboard.server.common.data.query.EntityCountQuery;
... ... @@ -40,10 +47,16 @@ import org.thingsboard.server.common.data.query.EntityListFilter;
40 47 import org.thingsboard.server.common.data.query.KeyFilter;
41 48 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
42 49 import org.thingsboard.server.common.data.security.Authority;
  50 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  51 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
43 52
  53 +import java.net.URI;
  54 +import java.net.URISyntaxException;
44 55 import java.util.ArrayList;
  56 +import java.util.Arrays;
45 57 import java.util.Collections;
46 58 import java.util.List;
  59 +import java.util.Random;
47 60 import java.util.stream.Collectors;
48 61
49 62 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
... ... @@ -190,23 +203,23 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
190 203 List<Device> devices = new ArrayList<>();
191 204 List<Long> temperatures = new ArrayList<>();
192 205 List<Long> highTemperatures = new ArrayList<>();
193   - for (int i=0;i<67;i++) {
  206 + for (int i = 0; i < 67; i++) {
194 207 Device device = new Device();
195   - String name = "Device"+i;
  208 + String name = "Device" + i;
196 209 device.setName(name);
197 210 device.setType("default");
198   - device.setLabel("testLabel"+(int)(Math.random()*1000));
199   - devices.add(doPost("/api/device?accessToken="+name, device, Device.class));
200   - long temperature = (long)(Math.random()*100);
  211 + device.setLabel("testLabel" + (int) (Math.random() * 1000));
  212 + devices.add(doPost("/api/device?accessToken=" + name, device, Device.class));
  213 + long temperature = (long) (Math.random() * 100);
201 214 temperatures.add(temperature);
202 215 if (temperature > 45) {
203 216 highTemperatures.add(temperature);
204 217 }
205 218 }
206   - for (int i=0;i<devices.size();i++) {
  219 + for (int i = 0; i < devices.size(); i++) {
207 220 Device device = devices.get(i);
208   - String payload = "{\"temperature\":"+temperatures.get(i)+"}";
209   - doPost("/api/plugins/telemetry/"+device.getId()+"/"+ DataConstants.SHARED_SCOPE, payload, String.class, status().isOk());
  221 + String payload = "{\"temperature\":" + temperatures.get(i) + "}";
  222 + doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, payload, String.class, status().isOk());
210 223 }
211 224 Thread.sleep(1000);
212 225
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import org.junit.After;
  19 +import org.junit.Assert;
  20 +import org.junit.Before;
  21 +import org.junit.Test;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.thingsboard.server.common.data.Device;
  24 +import org.thingsboard.server.common.data.Tenant;
  25 +import org.thingsboard.server.common.data.User;
  26 +import org.thingsboard.server.common.data.kv.Aggregation;
  27 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  28 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  29 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  30 +import org.thingsboard.server.common.data.page.PageData;
  31 +import org.thingsboard.server.common.data.query.DeviceTypeFilter;
  32 +import org.thingsboard.server.common.data.query.EntityData;
  33 +import org.thingsboard.server.common.data.query.EntityDataPageLink;
  34 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  35 +import org.thingsboard.server.common.data.query.TsValue;
  36 +import org.thingsboard.server.common.data.security.Authority;
  37 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  38 +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
  39 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
  40 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  41 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
  42 +
  43 +import java.util.Arrays;
  44 +import java.util.Collections;
  45 +import java.util.concurrent.TimeUnit;
  46 +
  47 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  48 +
  49 +public class BaseWebsocketApiTest extends AbstractWebsocketTest {
  50 +
  51 + private Tenant savedTenant;
  52 + private User tenantAdmin;
  53 + private TbTestWebSocketClient wsClient;
  54 +
  55 + @Autowired
  56 + private TimeseriesService tsService;
  57 +
  58 + @Before
  59 + public void beforeTest() throws Exception {
  60 + loginSysAdmin();
  61 +
  62 + Tenant tenant = new Tenant();
  63 + tenant.setTitle("My tenant");
  64 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  65 + Assert.assertNotNull(savedTenant);
  66 +
  67 + tenantAdmin = new User();
  68 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  69 + tenantAdmin.setTenantId(savedTenant.getId());
  70 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  71 + tenantAdmin.setFirstName("Joe");
  72 + tenantAdmin.setLastName("Downs");
  73 +
  74 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  75 +
  76 + wsClient = buildAndConnectWebSocketClient();
  77 + }
  78 +
  79 + @After
  80 + public void afterTest() throws Exception {
  81 + wsClient.close();
  82 +
  83 + loginSysAdmin();
  84 +
  85 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  86 + .andExpect(status().isOk());
  87 + }
  88 +
  89 + @Test
  90 + public void testEntityDataHistoryWsCmd() throws Exception {
  91 + Device device = new Device();
  92 + device.setName("Device");
  93 + device.setType("default");
  94 + device.setLabel("testLabel" + (int) (Math.random() * 1000));
  95 + device = doPost("/api/device", device, Device.class);
  96 +
  97 + long now = System.currentTimeMillis();
  98 +
  99 + DeviceTypeFilter dtf = new DeviceTypeFilter();
  100 + dtf.setDeviceNameFilter("D");
  101 + dtf.setDeviceType("default");
  102 + EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
  103 +
  104 + EntityHistoryCmd historyCmd = new EntityHistoryCmd();
  105 + historyCmd.setKeys(Arrays.asList("temperature"));
  106 + historyCmd.setAgg(Aggregation.NONE);
  107 + historyCmd.setLimit(1000);
  108 + historyCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1));
  109 + historyCmd.setEndTs(now);
  110 + EntityDataCmd cmd = new EntityDataCmd(1, edq, historyCmd, null, null);
  111 +
  112 + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
  113 + wrapper.setEntityDataCmds(Collections.singletonList(cmd));
  114 +
  115 + wsClient.send(mapper.writeValueAsString(wrapper));
  116 + String msg = wsClient.waitForReply();
  117 + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
  118 + Assert.assertEquals(1, update.getCmdId());
  119 + PageData<EntityData> pageData = update.getData();
  120 + Assert.assertNotNull(pageData);
  121 + Assert.assertEquals(1, pageData.getData().size());
  122 + Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
  123 + Assert.assertEquals(0, pageData.getData().get(0).getTimeseries().get("temperature").length);
  124 +
  125 + TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
  126 + TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 42L));
  127 + TsKvEntry dataPoint3 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(3), new LongDataEntry("temperature", 42L));
  128 + tsService.save(device.getTenantId(), device.getId(), Arrays.asList(dataPoint1, dataPoint2, dataPoint3), 0).get();
  129 +
  130 + wsClient.send(mapper.writeValueAsString(wrapper));
  131 + msg = wsClient.waitForReply();
  132 + update = mapper.readValue(msg, EntityDataUpdate.class);
  133 + Assert.assertEquals(1, update.getCmdId());
  134 + pageData = update.getData();
  135 + Assert.assertNotNull(pageData);
  136 + Assert.assertEquals(1, pageData.getData().size());
  137 + Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
  138 + TsValue[] tsArray = pageData.getData().get(0).getTimeseries().get("temperature");
  139 + Assert.assertEquals(3, tsArray.length);
  140 + Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]);
  141 + Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]);
  142 + Assert.assertEquals(new TsValue(dataPoint3.getTs(), dataPoint3.getValueAsString()), tsArray[2]);
  143 + }
  144 +
  145 +}
... ...
... ... @@ -26,6 +26,8 @@ import java.util.Arrays;
26 26
27 27 @RunWith(ClasspathSuite.class)
28 28 @ClasspathSuite.ClassnameFilters({
  29 +// "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
  30 +// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
29 31 "org.thingsboard.server.controller.sql.*Test",
30 32 })
31 33 public class ControllerSqlTestSuite {
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.java_websocket.client.WebSocketClient;
  20 +import org.java_websocket.handshake.ServerHandshake;
  21 +
  22 +import java.net.URI;
  23 +import java.nio.channels.NotYetConnectedException;
  24 +import java.util.concurrent.CountDownLatch;
  25 +import java.util.concurrent.TimeUnit;
  26 +
  27 +@Slf4j
  28 +public class TbTestWebSocketClient extends WebSocketClient {
  29 +
  30 + private volatile String lastMsg;
  31 + private volatile boolean replyReceived;
  32 + private CountDownLatch reply;
  33 +
  34 + public TbTestWebSocketClient(URI serverUri) {
  35 + super(serverUri);
  36 + }
  37 +
  38 + @Override
  39 + public void onOpen(ServerHandshake serverHandshake) {
  40 +
  41 + }
  42 +
  43 + @Override
  44 + public void onMessage(String s) {
  45 + if (!replyReceived) {
  46 + replyReceived = true;
  47 + lastMsg = s;
  48 + if (reply != null) {
  49 + reply.countDown();
  50 + }
  51 + }
  52 + }
  53 +
  54 + @Override
  55 + public void onClose(int i, String s, boolean b) {
  56 +
  57 + }
  58 +
  59 + @Override
  60 + public void onError(Exception e) {
  61 +
  62 + }
  63 +
  64 + @Override
  65 + public void send(String text) throws NotYetConnectedException {
  66 + reply = new CountDownLatch(1);
  67 + replyReceived = false;
  68 + super.send(text);
  69 + }
  70 +
  71 + public String waitForReply() {
  72 + try {
  73 + reply.await(3, TimeUnit.SECONDS);
  74 + } catch (InterruptedException e) {
  75 + log.warn("Failed to await reply", e);
  76 + }
  77 + return lastMsg;
  78 + }
  79 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.sql;
  17 +
  18 +import org.thingsboard.server.controller.BaseEntityQueryControllerTest;
  19 +import org.thingsboard.server.controller.BaseWebsocketApiTest;
  20 +import org.thingsboard.server.dao.service.DaoSqlTest;
  21 +
  22 +@DaoSqlTest
  23 +public class WebsocketApiSqlTest extends BaseWebsocketApiTest {
  24 +}
... ...
... ... @@ -37,7 +37,6 @@
37 37 <blackBoxTests.skip>true</blackBoxTests.skip>
38 38 <testcontainers.version>1.9.1</testcontainers.version>
39 39 <zeroturnaround.version>1.10</zeroturnaround.version>
40   - <java-websocket.version>1.3.9</java-websocket.version>
41 40 <httpclient.version>4.5.6</httpclient.version>
42 41 </properties>
43 42
... ... @@ -55,7 +54,6 @@
55 54 <dependency>
56 55 <groupId>org.java-websocket</groupId>
57 56 <artifactId>Java-WebSocket</artifactId>
58   - <version>${java-websocket.version}</version>
59 57 </dependency>
60 58 <dependency>
61 59 <groupId>org.apache.httpcomponents</groupId>
... ...
... ... @@ -105,6 +105,7 @@
105 105 <ua-parser.version>1.4.3</ua-parser.version>
106 106 <commons-beanutils.version>1.9.4</commons-beanutils.version>
107 107 <commons-collections.version>3.2.2</commons-collections.version>
  108 + <java-websocket.version>1.3.9</java-websocket.version>
108 109 </properties>
109 110
110 111 <modules>
... ... @@ -1346,6 +1347,12 @@
1346 1347 <artifactId>commons-collections</artifactId>
1347 1348 <version>${commons-collections.version}</version>
1348 1349 </dependency>
  1350 + <dependency>
  1351 + <groupId>org.java-websocket</groupId>
  1352 + <artifactId>Java-WebSocket</artifactId>
  1353 + <version>${java-websocket.version}</version>
  1354 + <scope>test</scope>
  1355 + </dependency>
1349 1356 </dependencies>
1350 1357 </dependencyManagement>
1351 1358
... ...