Commit 6823b8969f478ef6628ef7841add7b57aee147a4

Authored by Andrew Shvayka
1 parent a1662d66

Refactoring of Telemetry Websockets

Showing 17 changed files with 1209 additions and 362 deletions
... ... @@ -19,7 +19,7 @@ import java.util.Map;
19 19
20 20 import org.thingsboard.server.exception.ThingsboardErrorCode;
21 21 import org.thingsboard.server.exception.ThingsboardException;
22   -import org.thingsboard.server.controller.plugin.PluginWebSocketHandler;
  22 +import org.thingsboard.server.controller.plugin.TbWebSocketHandler;
23 23 import org.thingsboard.server.service.security.model.SecurityUser;
24 24 import org.springframework.context.annotation.Bean;
25 25 import org.springframework.context.annotation.Configuration;
... ... @@ -54,7 +54,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
54 54
55 55 @Override
56 56 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
57   - registry.addHandler(pluginWsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
  57 + registry.addHandler(wsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
58 58 .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() {
59 59
60 60 @Override
... ... @@ -82,8 +82,8 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
82 82 }
83 83
84 84 @Bean
85   - public WebSocketHandler pluginWsHandler() {
86   - return new PluginWebSocketHandler();
  85 + public WebSocketHandler wsHandler() {
  86 + return new TbWebSocketHandler();
87 87 }
88 88
89 89 protected SecurityUser getCurrentUser() throws ThingsboardException {
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + * <p>
  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 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
  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 + */
1 16 package org.thingsboard.server.controller;
2 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
3 19 import com.google.common.base.Function;
4 20 import com.google.common.util.concurrent.FutureCallback;
5 21 import com.google.common.util.concurrent.Futures;
6 22 import com.google.common.util.concurrent.ListenableFuture;
  23 +import com.google.gson.JsonElement;
  24 +import com.google.gson.JsonParser;
7 25 import lombok.extern.slf4j.Slf4j;
8 26 import org.springframework.beans.factory.annotation.Autowired;
9 27 import org.springframework.http.HttpStatus;
... ... @@ -11,47 +29,54 @@ import org.springframework.http.ResponseEntity;
11 29 import org.springframework.security.access.prepost.PreAuthorize;
12 30 import org.springframework.util.StringUtils;
13 31 import org.springframework.web.bind.annotation.PathVariable;
  32 +import org.springframework.web.bind.annotation.RequestBody;
14 33 import org.springframework.web.bind.annotation.RequestMapping;
15 34 import org.springframework.web.bind.annotation.RequestMethod;
16 35 import org.springframework.web.bind.annotation.RequestParam;
17   -import org.springframework.web.bind.annotation.ResponseStatus;
  36 +import org.springframework.web.bind.annotation.ResponseBody;
18 37 import org.springframework.web.bind.annotation.RestController;
19 38 import org.springframework.web.context.request.async.DeferredResult;
20   -import org.thingsboard.server.actors.plugin.ValidationResult;
21   -import org.thingsboard.server.common.data.Customer;
22 39 import org.thingsboard.server.common.data.DataConstants;
23   -import org.thingsboard.server.common.data.Device;
24   -import org.thingsboard.server.common.data.Tenant;
25   -import org.thingsboard.server.common.data.asset.Asset;
  40 +import org.thingsboard.server.common.data.EntityType;
26 41 import org.thingsboard.server.common.data.audit.ActionType;
27   -import org.thingsboard.server.common.data.id.AssetId;
28   -import org.thingsboard.server.common.data.id.CustomerId;
29   -import org.thingsboard.server.common.data.id.DeviceId;
30 42 import org.thingsboard.server.common.data.id.EntityId;
31 43 import org.thingsboard.server.common.data.id.EntityIdFactory;
32   -import org.thingsboard.server.common.data.id.RuleChainId;
33   -import org.thingsboard.server.common.data.id.TenantId;
34 44 import org.thingsboard.server.common.data.id.UUIDBased;
  45 +import org.thingsboard.server.common.data.kv.Aggregation;
35 46 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  47 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  48 +import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  49 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  50 +import org.thingsboard.server.common.data.kv.BooleanDataEntry;
  51 +import org.thingsboard.server.common.data.kv.DoubleDataEntry;
36 52 import org.thingsboard.server.common.data.kv.KvEntry;
  53 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  54 +import org.thingsboard.server.common.data.kv.StringDataEntry;
37 55 import org.thingsboard.server.common.data.kv.TsKvEntry;
38   -import org.thingsboard.server.common.data.rule.RuleChain;
  56 +import org.thingsboard.server.common.data.kv.TsKvQuery;
  57 +import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
  58 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
39 59 import org.thingsboard.server.dao.attributes.AttributesService;
40 60 import org.thingsboard.server.dao.timeseries.TimeseriesService;
41 61 import org.thingsboard.server.exception.ThingsboardException;
42   -import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
  62 +import org.thingsboard.server.extensions.api.exception.InvalidParametersException;
  63 +import org.thingsboard.server.extensions.api.exception.UncheckedApiException;
43 64 import org.thingsboard.server.extensions.api.plugins.PluginConstants;
44 65 import org.thingsboard.server.extensions.core.plugin.telemetry.AttributeData;
  66 +import org.thingsboard.server.extensions.core.plugin.telemetry.TsData;
  67 +import org.thingsboard.server.service.security.AccessValidator;
45 68 import org.thingsboard.server.service.security.model.SecurityUser;
  69 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
46 70
47 71 import javax.annotation.Nullable;
48 72 import javax.annotation.PreDestroy;
49 73 import java.util.ArrayList;
50 74 import java.util.Arrays;
  75 +import java.util.LinkedHashMap;
51 76 import java.util.List;
  77 +import java.util.Map;
52 78 import java.util.concurrent.ExecutorService;
53 79 import java.util.concurrent.Executors;
54   -import java.util.function.BiConsumer;
55 80 import java.util.stream.Collectors;
56 81
57 82 /**
... ... @@ -62,9 +87,8 @@ import java.util.stream.Collectors;
62 87 @Slf4j
63 88 public class TelemetryController extends BaseController {
64 89
65   - public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
66   - public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
67   - public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
  90 + @Autowired
  91 + private TelemetrySubscriptionService subscriptionService;
68 92
69 93 @Autowired
70 94 private AttributesService attributesService;
... ... @@ -72,6 +96,9 @@ public class TelemetryController extends BaseController {
72 96 @Autowired
73 97 private TimeseriesService tsService;
74 98
  99 + @Autowired
  100 + private AccessValidator accessValidator;
  101 +
75 102 private ExecutorService executor;
76 103
77 104 public void initExecutor() {
... ... @@ -87,117 +114,277 @@ public class TelemetryController extends BaseController {
87 114
88 115 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
89 116 @RequestMapping(value = "/{entityType}/{entityId}/keys/ATTRIBUTES", method = RequestMethod.GET)
90   - @ResponseStatus(value = HttpStatus.OK)
  117 + @ResponseBody
91 118 public DeferredResult<ResponseEntity> getAttributeKeys(
92 119 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
93   - return validateEntityAndCallback(entityType, entityIdStr,
94   - this::getAttributeKeysCallback,
95   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  120 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, this::getAttributeKeysCallback);
96 121 }
97 122
98 123 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
99 124 @RequestMapping(value = "/{entityType}/{entityId}/keys/ATTRIBUTES/{scope}", method = RequestMethod.GET)
100   - @ResponseStatus(value = HttpStatus.OK)
  125 + @ResponseBody
101 126 public DeferredResult<ResponseEntity> getAttributeKeysByScope(
102 127 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr
103 128 , @PathVariable("scope") String scope) throws ThingsboardException {
104   - return validateEntityAndCallback(entityType, entityIdStr,
105   - (result, entityId) -> getAttributeKeysCallback(result, entityId, scope),
106   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  129 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
  130 + (result, entityId) -> getAttributeKeysCallback(result, entityId, scope));
107 131 }
108 132
109 133 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
110 134 @RequestMapping(value = "/{entityType}/{entityId}/values/ATTRIBUTES", method = RequestMethod.GET)
111   - @ResponseStatus(value = HttpStatus.OK)
  135 + @ResponseBody
112 136 public DeferredResult<ResponseEntity> getAttributes(
113 137 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
114 138 @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
115 139 SecurityUser user = getCurrentUser();
116   - return validateEntityAndCallback(entityType, entityIdStr,
117   - (result, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr),
118   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  140 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
  141 + (result, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
119 142 }
120 143
121 144 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
122 145 @RequestMapping(value = "/{entityType}/{entityId}/values/ATTRIBUTES/{scope}", method = RequestMethod.GET)
123   - @ResponseStatus(value = HttpStatus.OK)
  146 + @ResponseBody
124 147 public DeferredResult<ResponseEntity> getAttributesByScope(
125 148 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
126 149 @PathVariable("scope") String scope,
127 150 @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
128 151 SecurityUser user = getCurrentUser();
129   - return validateEntityAndCallback(entityType, entityIdStr,
130   - (result, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr),
131   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  152 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
  153 + (result, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
132 154 }
133 155
134 156 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
135 157 @RequestMapping(value = "/{entityType}/{entityId}/keys/TIMESERIES", method = RequestMethod.GET)
136   - @ResponseStatus(value = HttpStatus.OK)
  158 + @ResponseBody
137 159 public DeferredResult<ResponseEntity> getTimeseriesKeys(
138 160 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
139   - return validateEntityAndCallback(entityType, entityIdStr,
  161 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
140 162 (result, entityId) -> {
141 163 Futures.addCallback(tsService.findAllLatest(entityId), getTsKeysToResponseCallback(result));
142   - },
143   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  164 + });
144 165 }
145 166
146 167 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
147 168 @RequestMapping(value = "/{entityType}/{entityId}/values/TIMESERIES", method = RequestMethod.GET)
148   - @ResponseStatus(value = HttpStatus.OK)
  169 + @ResponseBody
149 170 public DeferredResult<ResponseEntity> getLatestTimeseries(
150 171 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
151   - @PathVariable("scope") String scope,
152 172 @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
153 173 SecurityUser user = getCurrentUser();
154 174
155   - return validateEntityAndCallback(entityType, entityIdStr,
156   - (result, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr),
157   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  175 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
  176 + (result, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr));
158 177 }
159 178
160 179
161 180 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
162 181 @RequestMapping(value = "/{entityType}/{entityId}/values/TIMESERIES", method = RequestMethod.GET)
163   - @ResponseStatus(value = HttpStatus.OK)
164   - public DeferredResult<ResponseEntity> getLatestTimeseries(
  182 + @ResponseBody
  183 + public DeferredResult<ResponseEntity> getTimeseries(
165 184 @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
166   - @PathVariable("scope") String scope,
167   - @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
168   - SecurityUser user = getCurrentUser();
  185 + @RequestParam(name = "keys") String keys,
  186 + @RequestParam(name = "startTs") Long startTs,
  187 + @RequestParam(name = "endTs") Long endTs,
  188 + @RequestParam(name = "interval", defaultValue = "0") Long interval,
  189 + @RequestParam(name = "limit", defaultValue = "100") Integer limit,
  190 + @RequestParam(name = "agg", defaultValue = "NONE") String aggStr
  191 + ) throws ThingsboardException {
  192 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
  193 + (result, entityId) -> {
  194 + // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
  195 + Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
  196 + List<TsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg))
  197 + .collect(Collectors.toList());
  198 +
  199 + Futures.addCallback(tsService.findAll(entityId, queries), getTsKvListCallback(result));
  200 + });
  201 + }
  202 +
  203 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  204 + @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST)
  205 + @ResponseBody
  206 + public DeferredResult<ResponseEntity> saveDeviceAttributes(@PathVariable("deviceId") String deviceIdStr, @PathVariable("scope") String scope,
  207 + @RequestBody JsonNode request) throws ThingsboardException {
  208 + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
  209 + return saveAttributes(entityId, scope, request);
  210 + }
  211 +
  212 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  213 + @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST)
  214 + @ResponseBody
  215 + public DeferredResult<ResponseEntity> saveEntityAttributesV1(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  216 + @PathVariable("scope") String scope,
  217 + @RequestBody JsonNode request) throws ThingsboardException {
  218 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  219 + return saveAttributes(entityId, scope, request);
  220 + }
169 221
170   - return validateEntityAndCallback(entityType, entityIdStr,
171   - (result, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr),
172   - (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  222 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  223 + @RequestMapping(value = "/{entityType}/{entityId}/ATTRIBUTES/{scope}", method = RequestMethod.POST)
  224 + @ResponseBody
  225 + public DeferredResult<ResponseEntity> saveEntityAttributesV2(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  226 + @PathVariable("scope") String scope,
  227 + @RequestBody JsonNode request) throws ThingsboardException {
  228 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  229 + return saveAttributes(entityId, scope, request);
  230 + }
  231 +
  232 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  233 + @RequestMapping(value = "/{entityType}/{entityId}/TIMESERIES/{scope}", method = RequestMethod.POST)
  234 + @ResponseBody
  235 + public DeferredResult<ResponseEntity> saveEntityTelemetry(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  236 + @PathVariable("scope") String scope,
  237 + @RequestBody String requestBody) throws ThingsboardException {
  238 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  239 + return saveTelemetry(entityId, requestBody, 0L);
  240 + }
  241 +
  242 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  243 + @RequestMapping(value = "/{entityType}/{entityId}/TIMESERIES/{scope}/{ttl}", method = RequestMethod.POST)
  244 + @ResponseBody
  245 + public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  246 + @PathVariable("scope") String scope, @PathVariable("ttl") Long ttl,
  247 + @RequestBody String requestBody) throws ThingsboardException {
  248 + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  249 + return saveTelemetry(entityId, requestBody, ttl);
173 250 }
174 251
175   - private DeferredResult<ResponseEntity> validateEntityAndCallback(String entityType, String entityIdStr,
176   - BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess, BiConsumer<DeferredResult<ResponseEntity>, Throwable> onFailure) throws ThingsboardException {
177   - final DeferredResult<ResponseEntity> response = new DeferredResult<>();
  252 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  253 + @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
  254 + @ResponseBody
  255 + public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("deviceId") String deviceIdStr,
  256 + @PathVariable("scope") String scope,
  257 + @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
  258 + EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
  259 + return deleteAttributes(entityId, scope, keysStr);
  260 + }
  261 +
  262 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  263 + @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE)
  264 + @ResponseBody
  265 + public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  266 + @PathVariable("scope") String scope,
  267 + @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
178 268 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
  269 + return deleteAttributes(entityId, scope, keysStr);
  270 + }
  271 +
  272 + private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdStr, String scope, String keysStr) throws ThingsboardException {
  273 + List<String> keys = toKeysList(keysStr);
  274 + if (keys.isEmpty()) {
  275 + return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST);
  276 + }
  277 + SecurityUser user = getCurrentUser();
  278 + if (DataConstants.SERVER_SCOPE.equals(scope) ||
  279 + DataConstants.SHARED_SCOPE.equals(scope) ||
  280 + DataConstants.CLIENT_SCOPE.equals(scope)) {
  281 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdStr, (result, entityId) -> {
  282 + ListenableFuture<List<Void>> future = attributesService.removeAll(entityId, scope, keys);
  283 + Futures.addCallback(future, new FutureCallback<List<Void>>() {
  284 + @Override
  285 + public void onSuccess(@Nullable List<Void> tmp) {
  286 + logAttributesDeleted(user, entityId, scope, keys, null);
  287 + result.setResult(new ResponseEntity<>(HttpStatus.OK));
  288 + }
179 289
180   - validate(getCurrentUser(), entityId, new ValidationCallback(response,
181   - new FutureCallback<DeferredResult<ResponseEntity>>() {
182 290 @Override
183   - public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
184   - onSuccess.accept(response, entityId);
  291 + public void onFailure(Throwable t) {
  292 + logAttributesDeleted(user, entityId, scope, keys, t);
  293 + result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  294 + }
  295 + }, executor);
  296 + });
  297 + } else {
  298 + return getImmediateDeferredResult("Invalid attribute scope: " + scope, HttpStatus.BAD_REQUEST);
  299 + }
  300 + }
  301 +
  302 + private DeferredResult<ResponseEntity> saveAttributes(EntityId entityIdSrc, String scope, JsonNode json) throws ThingsboardException {
  303 + if (!DataConstants.SERVER_SCOPE.equals(scope) && !DataConstants.SHARED_SCOPE.equals(scope)) {
  304 + return getImmediateDeferredResult("Invalid scope: " + scope, HttpStatus.BAD_REQUEST);
  305 + }
  306 + if (json.isObject()) {
  307 + List<AttributeKvEntry> attributes = extractRequestAttributes(json);
  308 + if (attributes.isEmpty()) {
  309 + return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
  310 + }
  311 + SecurityUser user = getCurrentUser();
  312 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdSrc, (result, entityId) -> {
  313 + ListenableFuture<List<Void>> future = attributesService.save(entityId, scope, attributes);
  314 + Futures.addCallback(future, new FutureCallback<List<Void>>() {
  315 + @Override
  316 + public void onSuccess(@Nullable List<Void> tmp) {
  317 + logAttributesUpdated(user, entityId, scope, attributes, null);
  318 + result.setResult(new ResponseEntity(HttpStatus.OK));
  319 + subscriptionService.onAttributesUpdateFromServer(entityId, scope, attributes);
185 320 }
186 321
187 322 @Override
188 323 public void onFailure(Throwable t) {
189   - onFailure.accept(response, t);
  324 + logAttributesUpdated(user, entityId, scope, attributes, t);
  325 + AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
190 326 }
191   - }));
  327 + });
  328 + result.setResult(new ResponseEntity(HttpStatus.OK));
  329 + });
  330 + } else {
  331 + return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST);
  332 + }
  333 + }
  334 +
  335 + private DeferredResult<ResponseEntity> saveTelemetry(EntityId entityIdSrc, String requestBody, long ttl) throws ThingsboardException {
  336 + TelemetryUploadRequest telemetryRequest;
  337 + JsonElement telemetryJson;
  338 + try {
  339 + telemetryJson = new JsonParser().parse(requestBody);
  340 + } catch (Exception e) {
  341 + return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
  342 + }
  343 + try {
  344 + telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson);
  345 + } catch (Exception e) {
  346 + return getImmediateDeferredResult("Unable to parse timeseries payload. Invalid JSON body: " + e.getMessage(), HttpStatus.BAD_REQUEST);
  347 + }
  348 + List<TsKvEntry> entries = new ArrayList<>();
  349 + for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.getData().entrySet()) {
  350 + for (KvEntry kv : entry.getValue()) {
  351 + entries.add(new BasicTsKvEntry(entry.getKey(), kv));
  352 + }
  353 + }
  354 + if (entries.isEmpty()) {
  355 + return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST);
  356 + }
  357 + SecurityUser user = getCurrentUser();
  358 + return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdSrc, (result, entityId) -> {
  359 + ListenableFuture<List<Void>> future = tsService.save(entityId, entries, ttl);
  360 + Futures.addCallback(future, new FutureCallback<List<Void>>() {
  361 + @Override
  362 + public void onSuccess(@Nullable List<Void> tmp) {
  363 + result.setResult(new ResponseEntity(HttpStatus.OK));
  364 + subscriptionService.onTimeseriesUpdateFromServer(entityId, entries);
  365 + }
192 366
193   - return response;
  367 + @Override
  368 + public void onFailure(Throwable t) {
  369 + AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
  370 + }
  371 + });
  372 + result.setResult(new ResponseEntity(HttpStatus.OK));
  373 + });
194 374 }
195 375
196   - private void getAttributeValuesCallback(@Nullable DeferredResult<ResponseEntity> result, SecurityUser user, EntityId entityId, String scope, String keys) {
197   - List<String> keyList = null;
198   - if (!StringUtils.isEmpty(keys)) {
199   - keyList = Arrays.asList(keys.split(","));
  376 + private void getLatestTimeseriesValuesCallback(@Nullable DeferredResult<ResponseEntity> result, SecurityUser user, EntityId entityId, String keys) {
  377 + ListenableFuture<List<TsKvEntry>> future;
  378 + if (StringUtils.isEmpty(keys)) {
  379 + future = tsService.findAllLatest(entityId);
  380 + } else {
  381 + future = tsService.findLatest(entityId, toKeysList(keys));
200 382 }
  383 + Futures.addCallback(future, getTsKvListCallback(result));
  384 + }
  385 +
  386 + private void getAttributeValuesCallback(@Nullable DeferredResult<ResponseEntity> result, SecurityUser user, EntityId entityId, String scope, String keys) {
  387 + List<String> keyList = toKeysList(keys);
201 388 FutureCallback<List<AttributeKvEntry>> callback = getAttributeValuesToResponseCallback(result, user, scope, entityId, keyList);
202 389 if (!StringUtils.isEmpty(scope)) {
203 390 if (keyList != null && !keyList.isEmpty()) {
... ... @@ -247,7 +434,7 @@ public class TelemetryController extends BaseController {
247 434 @Override
248 435 public void onFailure(Throwable e) {
249 436 log.error("Failed to fetch attributes", e);
250   - handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
  437 + AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
251 438 }
252 439 };
253 440 }
... ... @@ -264,12 +451,13 @@ public class TelemetryController extends BaseController {
264 451 @Override
265 452 public void onFailure(Throwable e) {
266 453 log.error("Failed to fetch attributes", e);
267   - handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
  454 + AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
268 455 }
269 456 };
270 457 }
271 458
272   - private FutureCallback<List<AttributeKvEntry>> getAttributeValuesToResponseCallback(final DeferredResult<ResponseEntity> response, final SecurityUser user, final String scope,
  459 + private FutureCallback<List<AttributeKvEntry>> getAttributeValuesToResponseCallback(final DeferredResult<ResponseEntity> response,
  460 + final SecurityUser user, final String scope,
273 461 final EntityId entityId, final List<String> keyList) {
274 462 return new FutureCallback<List<AttributeKvEntry>>() {
275 463 @Override
... ... @@ -284,12 +472,32 @@ public class TelemetryController extends BaseController {
284 472 public void onFailure(Throwable e) {
285 473 log.error("Failed to fetch attributes", e);
286 474 logAttributesRead(user, entityId, scope, keyList, e);
287   - handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
  475 + AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
288 476 }
289 477 };
290 478 }
291 479
292   - private void logAttributesRead(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
  480 + private FutureCallback<List<TsKvEntry>> getTsKvListCallback(final DeferredResult<ResponseEntity> response) {
  481 + return new FutureCallback<List<TsKvEntry>>() {
  482 + @Override
  483 + public void onSuccess(List<TsKvEntry> data) {
  484 + Map<String, List<TsData>> result = new LinkedHashMap<>();
  485 + for (TsKvEntry entry : data) {
  486 + result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>())
  487 + .add(new TsData(entry.getTs(), entry.getValueAsString()));
  488 + }
  489 + response.setResult(new ResponseEntity<>(result, HttpStatus.OK));
  490 + }
  491 +
  492 + @Override
  493 + public void onFailure(Throwable e) {
  494 + log.error("Failed to fetch historical data", e);
  495 + AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
  496 + }
  497 + };
  498 + }
  499 +
  500 + private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
293 501 auditLogService.logEntityAction(
294 502 user.getTenantId(),
295 503 user.getCustomerId(),
... ... @@ -297,163 +505,39 @@ public class TelemetryController extends BaseController {
297 505 user.getName(),
298 506 (UUIDBased & EntityId) entityId,
299 507 null,
300   - ActionType.ATTRIBUTES_READ,
  508 + ActionType.ATTRIBUTES_DELETED,
301 509 toException(e),
302 510 scope,
303 511 keys);
304 512 }
305 513
306   - private void handleError(Throwable e, final DeferredResult<ResponseEntity> response, HttpStatus defaultErrorStatus) {
307   - ResponseEntity responseEntity;
308   - if (e != null && e instanceof ToErrorResponseEntity) {
309   - responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
310   - } else if (e != null && e instanceof IllegalArgumentException) {
311   - responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
312   - } else {
313   - responseEntity = new ResponseEntity<>(defaultErrorStatus);
314   - }
315   - response.setResult(responseEntity);
316   - }
317   -
318   - private void validate(SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
319   - switch (entityId.getEntityType()) {
320   - case DEVICE:
321   - validateDevice(currentUser, entityId, callback);
322   - return;
323   - case ASSET:
324   - validateAsset(currentUser, entityId, callback);
325   - return;
326   - case RULE_CHAIN:
327   - validateRuleChain(currentUser, entityId, callback);
328   - return;
329   - case CUSTOMER:
330   - validateCustomer(currentUser, entityId, callback);
331   - return;
332   - case TENANT:
333   - validateTenant(currentUser, entityId, callback);
334   - return;
335   - default:
336   - //TODO: add support of other entities
337   - throw new IllegalStateException("Not Implemented!");
338   - }
339   - }
340   -
341   - private void validateDevice(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
342   - if (currentUser.isSystemAdmin()) {
343   - callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
344   - } else {
345   - ListenableFuture<Device> deviceFuture = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
346   - Futures.addCallback(deviceFuture, getCallback(callback, device -> {
347   - if (device == null) {
348   - return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND);
349   - } else {
350   - if (!device.getTenantId().equals(currentUser.getTenantId())) {
351   - return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!");
352   - } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
353   - return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
354   - } else {
355   - return ValidationResult.ok();
356   - }
357   - }
358   - }));
359   - }
360   - }
361   -
362   - private void validateAsset(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
363   - if (currentUser.isSystemAdmin()) {
364   - callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
365   - } else {
366   - ListenableFuture<Asset> assetFuture = assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
367   - Futures.addCallback(assetFuture, getCallback(callback, asset -> {
368   - if (asset == null) {
369   - return ValidationResult.entityNotFound("Asset with requested id wasn't found!");
370   - } else {
371   - if (!asset.getTenantId().equals(currentUser.getTenantId())) {
372   - return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!");
373   - } else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
374   - return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
375   - } else {
376   - return ValidationResult.ok();
377   - }
378   - }
379   - }));
380   - }
381   - }
382   -
383   -
384   - private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
385   - if (currentUser.isCustomerUser()) {
386   - callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
387   - } else {
388   - ListenableFuture<RuleChain> ruleChainFuture = ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId()));
389   - Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> {
390   - if (ruleChain == null) {
391   - return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!");
392   - } else {
393   - if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
394   - return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
395   - } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
396   - return ValidationResult.accessDenied("Rule chain is not in system scope!");
397   - } else {
398   - return ValidationResult.ok();
399   - }
400   - }
401   - }));
402   - }
403   - }
404   -
405   - private void validateCustomer(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
406   - if (currentUser.isSystemAdmin()) {
407   - callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
408   - } else {
409   - ListenableFuture<Customer> customerFuture = customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
410   - Futures.addCallback(customerFuture, getCallback(callback, customer -> {
411   - if (customer == null) {
412   - return ValidationResult.entityNotFound("Customer with requested id wasn't found!");
413   - } else {
414   - if (!customer.getTenantId().equals(currentUser.getTenantId())) {
415   - return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!");
416   - } else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
417   - return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
418   - } else {
419   - return ValidationResult.ok();
420   - }
421   - }
422   - }));
423   - }
424   - }
425   -
426   - private void validateTenant(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
427   - if (currentUser.isCustomerUser()) {
428   - callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
429   - } else if (currentUser.isSystemAdmin()) {
430   - callback.onSuccess(ValidationResult.ok());
431   - } else {
432   - ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
433   - Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
434   - if (tenant == null) {
435   - return ValidationResult.entityNotFound("Tenant with requested id wasn't found!");
436   - } else if (!tenant.getId().equals(currentUser.getTenantId())) {
437   - return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
438   - } else {
439   - return ValidationResult.ok();
440   - }
441   - }));
442   - }
  514 + private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List<AttributeKvEntry> attributes, Throwable e) {
  515 + auditLogService.logEntityAction(
  516 + user.getTenantId(),
  517 + user.getCustomerId(),
  518 + user.getId(),
  519 + user.getName(),
  520 + (UUIDBased & EntityId) entityId,
  521 + null,
  522 + ActionType.ATTRIBUTES_UPDATED,
  523 + toException(e),
  524 + scope,
  525 + attributes);
443 526 }
444 527
445   - private <T> FutureCallback<T> getCallback(ValidationCallback callback, Function<T, ValidationResult> transformer) {
446   - return new FutureCallback<T>() {
447   - @Override
448   - public void onSuccess(@Nullable T result) {
449   - callback.onSuccess(transformer.apply(result));
450   - }
451 528
452   - @Override
453   - public void onFailure(Throwable t) {
454   - callback.onFailure(t);
455   - }
456   - };
  529 + private void logAttributesRead(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
  530 + auditLogService.logEntityAction(
  531 + user.getTenantId(),
  532 + user.getCustomerId(),
  533 + user.getId(),
  534 + user.getName(),
  535 + (UUIDBased & EntityId) entityId,
  536 + null,
  537 + ActionType.ATTRIBUTES_READ,
  538 + toException(e),
  539 + scope,
  540 + keys);
457 541 }
458 542
459 543 private ListenableFuture<List<AttributeKvEntry>> mergeAllAttributesFutures(List<ListenableFuture<List<AttributeKvEntry>>> futures) {
... ... @@ -467,4 +551,40 @@ public class TelemetryController extends BaseController {
467 551 }, executor);
468 552 }
469 553
  554 + private List<String> toKeysList(String keys) {
  555 + List<String> keyList = null;
  556 + if (!StringUtils.isEmpty(keys)) {
  557 + keyList = Arrays.asList(keys.split(","));
  558 + }
  559 + return keyList;
  560 + }
  561 +
  562 + private DeferredResult<ResponseEntity> getImmediateDeferredResult(String message, HttpStatus status) {
  563 + DeferredResult<ResponseEntity> result = new DeferredResult<>();
  564 + result.setResult(new ResponseEntity<>(message, status));
  565 + return result;
  566 + }
  567 +
  568 + private List<AttributeKvEntry> extractRequestAttributes(JsonNode jsonNode) {
  569 + long ts = System.currentTimeMillis();
  570 + List<AttributeKvEntry> attributes = new ArrayList<>();
  571 + jsonNode.fields().forEachRemaining(entry -> {
  572 + String key = entry.getKey();
  573 + JsonNode value = entry.getValue();
  574 + if (entry.getValue().isTextual()) {
  575 + attributes.add(new BaseAttributeKvEntry(new StringDataEntry(key, value.textValue()), ts));
  576 + } else if (entry.getValue().isBoolean()) {
  577 + attributes.add(new BaseAttributeKvEntry(new BooleanDataEntry(key, value.booleanValue()), ts));
  578 + } else if (entry.getValue().isDouble()) {
  579 + attributes.add(new BaseAttributeKvEntry(new DoubleDataEntry(key, value.doubleValue()), ts));
  580 + } else if (entry.getValue().isNumber()) {
  581 + if (entry.getValue().isBigInteger()) {
  582 + throw new UncheckedApiException(new InvalidParametersException("Big integer values are not supported!"));
  583 + } else {
  584 + attributes.add(new BaseAttributeKvEntry(new LongDataEntry(key, value.longValue()), ts));
  585 + }
  586 + }
  587 + });
  588 + return attributes;
  589 + }
470 590 }
... ...
1 1 /**
2 2 * Copyright © 2016-2018 The Thingsboard Authors
3   - * <p>
  3 + *
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7   - * <p>
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - * <p>
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
... ...
... ... @@ -48,59 +48,59 @@ import javax.servlet.http.HttpServletRequest;
48 48 @Slf4j
49 49 public class PluginApiController extends BaseController {
50 50
51   - @SuppressWarnings("rawtypes")
52   - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
53   - @RequestMapping(value = "/{pluginToken}/**")
54   - @ResponseStatus(value = HttpStatus.OK)
55   - public DeferredResult<ResponseEntity> processRequest(
56   - @PathVariable("pluginToken") String pluginToken,
57   - RequestEntity<byte[]> requestEntity,
58   - HttpServletRequest request)
59   - throws ThingsboardException {
60   - log.debug("[{}] Going to process requst uri: {}", pluginToken, requestEntity.getUrl());
61   - DeferredResult<ResponseEntity> result = new DeferredResult<ResponseEntity>();
62   - PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
63   - if (pluginMd == null) {
64   - result.setErrorResult(new PluginNotFoundException("Plugin with token: " + pluginToken + " not found!"));
65   - } else {
66   - TenantId tenantId = getCurrentUser().getTenantId();
67   - CustomerId customerId = getCurrentUser().getCustomerId();
68   - if (validatePluginAccess(pluginMd, tenantId, customerId)) {
69   - if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){
70   - tenantId = null;
71   - }
72   - UserId userId = getCurrentUser().getId();
73   - String userName = getCurrentUser().getName();
74   - PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(),
75   - tenantId, customerId, userId, userName);
76   - actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
77   - } else {
78   - result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
79   - }
80   -
81   - }
82   - return result;
83   - }
84   -
85   - public static boolean validatePluginAccess(PluginMetaData pluginMd, TenantId tenantId, CustomerId customerId) {
86   - boolean systemAdministrator = tenantId == null || ModelConstants.NULL_UUID.equals(tenantId.getId());
87   - boolean tenantAdministrator = !systemAdministrator && (customerId == null || ModelConstants.NULL_UUID.equals(customerId.getId()));
88   - boolean systemPlugin = ModelConstants.NULL_UUID.equals(pluginMd.getTenantId().getId());
89   -
90   - boolean validUser = false;
91   - if (systemPlugin) {
92   - if (pluginMd.isPublicAccess() || systemAdministrator) {
93   - // All users can access public system plugins. Only system
94   - // users can access private system plugins
95   - validUser = true;
96   - }
97   - } else {
98   - if ((pluginMd.isPublicAccess() || tenantAdministrator) && tenantId != null && tenantId.equals(pluginMd.getTenantId())) {
99   - // All tenant users can access public tenant plugins. Only tenant
100   - // administrator can access private tenant plugins
101   - validUser = true;
102   - }
103   - }
104   - return validUser;
105   - }
  51 +// @SuppressWarnings("rawtypes")
  52 +// @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  53 +// @RequestMapping(value = "/{pluginToken}/**")
  54 +// @ResponseStatus(value = HttpStatus.OK)
  55 +// public DeferredResult<ResponseEntity> processRequest(
  56 +// @PathVariable("pluginToken") String pluginToken,
  57 +// RequestEntity<byte[]> requestEntity,
  58 +// HttpServletRequest request)
  59 +// throws ThingsboardException {
  60 +// log.debug("[{}] Going to process requst uri: {}", pluginToken, requestEntity.getUrl());
  61 +// DeferredResult<ResponseEntity> result = new DeferredResult<ResponseEntity>();
  62 +// PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
  63 +// if (pluginMd == null) {
  64 +// result.setErrorResult(new PluginNotFoundException("Plugin with token: " + pluginToken + " not found!"));
  65 +// } else {
  66 +// TenantId tenantId = getCurrentUser().getTenantId();
  67 +// CustomerId customerId = getCurrentUser().getCustomerId();
  68 +// if (validatePluginAccess(pluginMd, tenantId, customerId)) {
  69 +// if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){
  70 +// tenantId = null;
  71 +// }
  72 +// UserId userId = getCurrentUser().getId();
  73 +// String userName = getCurrentUser().getName();
  74 +// PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(),
  75 +// tenantId, customerId, userId, userName);
  76 +// actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
  77 +// } else {
  78 +// result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
  79 +// }
  80 +//
  81 +// }
  82 +// return result;
  83 +// }
  84 +//
  85 +// public static boolean validatePluginAccess(PluginMetaData pluginMd, TenantId tenantId, CustomerId customerId) {
  86 +// boolean systemAdministrator = tenantId == null || ModelConstants.NULL_UUID.equals(tenantId.getId());
  87 +// boolean tenantAdministrator = !systemAdministrator && (customerId == null || ModelConstants.NULL_UUID.equals(customerId.getId()));
  88 +// boolean systemPlugin = ModelConstants.NULL_UUID.equals(pluginMd.getTenantId().getId());
  89 +//
  90 +// boolean validUser = false;
  91 +// if (systemPlugin) {
  92 +// if (pluginMd.isPublicAccess() || systemAdministrator) {
  93 +// // All users can access public system plugins. Only system
  94 +// // users can access private system plugins
  95 +// validUser = true;
  96 +// }
  97 +// } else {
  98 +// if ((pluginMd.isPublicAccess() || tenantAdministrator) && tenantId != null && tenantId.equals(pluginMd.getTenantId())) {
  99 +// // All tenant users can access public tenant plugins. Only tenant
  100 +// // administrator can access private tenant plugins
  101 +// validUser = true;
  102 +// }
  103 +// }
  104 +// return validUser;
  105 +// }
106 106 }
... ...
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java renamed from application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java
1 1 /**
2 2 * Copyright © 2016-2018 The Thingsboard Authors
3   - *
  3 + * <p>
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
  7 + * <p>
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + * <p>
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
12 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
... ... @@ -15,57 +15,42 @@
15 15 */
16 16 package org.thingsboard.server.controller.plugin;
17 17
18   -import java.io.IOException;
19   -import java.net.URI;
20   -import java.security.InvalidParameterException;
21   -import java.util.UUID;
22   -import java.util.concurrent.ConcurrentHashMap;
23   -import java.util.concurrent.ConcurrentMap;
24   -
25 18 import lombok.extern.slf4j.Slf4j;
26 19 import org.springframework.beans.factory.BeanCreationNotAllowedException;
27   -import org.springframework.context.annotation.Lazy;
28   -import org.springframework.web.bind.annotation.RequestMapping;
29   -import org.springframework.web.bind.annotation.RestController;
30   -import org.thingsboard.server.actors.service.ActorService;
31   -import org.thingsboard.server.common.data.id.UserId;
32   -import org.thingsboard.server.config.WebSocketConfiguration;
33   -import org.thingsboard.server.extensions.api.plugins.PluginConstants;
34   -import org.thingsboard.server.service.security.model.SecurityUser;
35   -import org.thingsboard.server.common.data.id.CustomerId;
36   -import org.thingsboard.server.common.data.id.TenantId;
37   -import org.thingsboard.server.common.data.plugin.PluginMetaData;
38   -import org.thingsboard.server.dao.plugin.PluginService;
39   -import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
40   -import org.thingsboard.server.extensions.api.plugins.ws.BasicPluginWebsocketSessionRef;
41   -import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
42   -import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
43   -import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
44   -import org.thingsboard.server.extensions.api.plugins.ws.msg.SessionEventPluginWebSocketMsg;
45   -import org.thingsboard.server.extensions.api.plugins.ws.msg.TextPluginWebSocketMsg;
46   -import org.slf4j.Logger;
47   -import org.slf4j.LoggerFactory;
48 20 import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.context.annotation.Lazy;
49 22 import org.springframework.stereotype.Service;
50 23 import org.springframework.web.socket.CloseStatus;
51 24 import org.springframework.web.socket.TextMessage;
52 25 import org.springframework.web.socket.WebSocketSession;
53 26 import org.springframework.web.socket.handler.TextWebSocketHandler;
  27 +import org.thingsboard.server.config.WebSocketConfiguration;
  28 +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
  29 +import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
  30 +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
  31 +import org.thingsboard.server.extensions.api.plugins.ws.msg.TextPluginWebSocketMsg;
  32 +import org.thingsboard.server.service.security.model.SecurityUser;
  33 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint;
  34 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
  35 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  36 +
  37 +import java.io.IOException;
  38 +import java.net.URI;
  39 +import java.security.InvalidParameterException;
  40 +import java.util.UUID;
  41 +import java.util.concurrent.ConcurrentHashMap;
  42 +import java.util.concurrent.ConcurrentMap;
54 43
55 44 @Service
56 45 @Slf4j
57   -public class PluginWebSocketHandler extends TextWebSocketHandler implements PluginWebSocketMsgEndpoint {
  46 +public class TbWebSocketHandler extends TextWebSocketHandler implements PluginWebSocketMsgEndpoint, TelemetryWebSocketMsgEndpoint {
58 47
59 48 private static final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
60 49 private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
61 50
62 51 @Autowired
63 52 @Lazy
64   - private ActorService actorService;
65   -
66   - @Autowired
67   - @Lazy
68   - private PluginService pluginService;
  53 + private TelemetryWebSocketService webSocketService;
69 54
70 55 @Override
71 56 public void handleTextMessage(WebSocketSession session, TextMessage message) {
... ... @@ -73,7 +58,7 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
73 58 log.info("[{}] Processing {}", session.getId(), message);
74 59 SessionMetaData sessionMd = internalSessionMap.get(session.getId());
75 60 if (sessionMd != null) {
76   - actorService.process(new TextPluginWebSocketMsg(sessionMd.sessionRef, message.getPayload()));
  61 + webSocketService.handleWebSocketMsg(sessionMd.sessionRef, message.getPayload());
77 62 } else {
78 63 log.warn("[{}] Failed to find session", session.getId());
79 64 session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!"));
... ... @@ -88,11 +73,11 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
88 73 super.afterConnectionEstablished(session);
89 74 try {
90 75 String internalSessionId = session.getId();
91   - PluginWebsocketSessionRef sessionRef = toRef(session);
  76 + TelemetryWebSocketSessionRef sessionRef = toRef(session);
92 77 String externalSessionId = sessionRef.getSessionId();
93 78 internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef));
94 79 externalSessionMap.put(externalSessionId, internalSessionId);
95   - actorService.process(new SessionEventPluginWebSocketMsg(sessionRef, SessionEvent.onEstablished()));
  80 + processInWebSocketService(sessionRef, SessionEvent.onEstablished());
96 81 log.info("[{}][{}] Session is started", externalSessionId, session.getId());
97 82 } catch (InvalidParameterException e) {
98 83 log.warn("[[{}] Failed to start session", session.getId(), e);
... ... @@ -108,7 +93,7 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
108 93 super.handleTransportError(session, tError);
109 94 SessionMetaData sessionMd = internalSessionMap.get(session.getId());
110 95 if (sessionMd != null) {
111   - processInActorService(new SessionEventPluginWebSocketMsg(sessionMd.sessionRef, SessionEvent.onError(tError)));
  96 + processInWebSocketService(sessionMd.sessionRef, SessionEvent.onError(tError));
112 97 } else {
113 98 log.warn("[{}] Failed to find session", session.getId());
114 99 }
... ... @@ -121,20 +106,20 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
121 106 SessionMetaData sessionMd = internalSessionMap.remove(session.getId());
122 107 if (sessionMd != null) {
123 108 externalSessionMap.remove(sessionMd.sessionRef.getSessionId());
124   - processInActorService(new SessionEventPluginWebSocketMsg(sessionMd.sessionRef, SessionEvent.onClosed()));
  109 + processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
125 110 }
126 111 log.info("[{}] Session is closed", session.getId());
127 112 }
128 113
129   - private void processInActorService(SessionEventPluginWebSocketMsg msg) {
  114 + private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {
130 115 try {
131   - actorService.process(msg);
  116 + webSocketService.handleWebSocketSessionEvent(sessionRef, event);
132 117 } catch (BeanCreationNotAllowedException e) {
133   - log.warn("[{}] Failed to close session due to possible shutdown state", msg.getSessionRef().getSessionId());
  118 + log.warn("[{}] Failed to close session due to possible shutdown state", sessionRef.getSessionId());
134 119 }
135 120 }
136 121
137   - private PluginWebsocketSessionRef toRef(WebSocketSession session) throws IOException {
  122 + private TelemetryWebSocketSessionRef toRef(WebSocketSession session) throws IOException {
138 123 URI sessionUri = session.getUri();
139 124 String path = sessionUri.getPath();
140 125 path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length());
... ... @@ -142,33 +127,20 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
142 127 throw new IllegalArgumentException("URL should contain plugin token!");
143 128 }
144 129 String[] pathElements = path.split("/");
145   - String pluginToken = pathElements[0];
146   - // TODO: cache
147   - PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
148   - if (pluginMd == null) {
  130 + String serviceToken = pathElements[0];
  131 + if (!"telemetry".equalsIgnoreCase(serviceToken)) {
149 132 throw new InvalidParameterException("Can't find plugin with specified token!");
150 133 } else {
151 134 SecurityUser currentUser = (SecurityUser) session.getAttributes().get(WebSocketConfiguration.WS_SECURITY_USER_ATTRIBUTE);
152   - TenantId tenantId = currentUser.getTenantId();
153   - CustomerId customerId = currentUser.getCustomerId();
154   - if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) {
155   - UserId userId = currentUser.getId();
156   - String userName = currentUser.getName();
157   - PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId,
158   - currentUser.getCustomerId(), userId, userName);
159   - return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(),
160   - session.getLocalAddress(), session.getRemoteAddress());
161   - } else {
162   - throw new SecurityException("Current user is not allowed to use this plugin!");
163   - }
  135 + return new TelemetryWebSocketSessionRef(UUID.randomUUID().toString(), currentUser, session.getLocalAddress(), session.getRemoteAddress());
164 136 }
165 137 }
166 138
167 139 private static class SessionMetaData {
168 140 private final WebSocketSession session;
169   - private final PluginWebsocketSessionRef sessionRef;
  141 + private final TelemetryWebSocketSessionRef sessionRef;
170 142
171   - public SessionMetaData(WebSocketSession session, PluginWebsocketSessionRef sessionRef) {
  143 + public SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) {
172 144 super();
173 145 this.session = session;
174 146 this.sessionRef = sessionRef;
... ... @@ -176,6 +148,41 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
176 148 }
177 149
178 150 @Override
  151 + public void send(TelemetryWebSocketSessionRef sessionRef, String msg) throws IOException {
  152 + String externalId = sessionRef.getSessionId();
  153 + log.debug("[{}] Processing {}", externalId, msg);
  154 + String internalId = externalSessionMap.get(externalId);
  155 + if (internalId != null) {
  156 + SessionMetaData sessionMd = internalSessionMap.get(internalId);
  157 + if (sessionMd != null) {
  158 + sessionMd.session.sendMessage(new TextMessage(msg));
  159 + } else {
  160 + log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
  161 + }
  162 + } else {
  163 + log.warn("[{}] Failed to find session by external id", externalId);
  164 + }
  165 + }
  166 +
  167 + @Override
  168 + public void close(TelemetryWebSocketSessionRef sessionRef) throws IOException {
  169 + String externalId = sessionRef.getSessionId();
  170 + log.debug("[{}] Processing close request", externalId);
  171 + String internalId = externalSessionMap.get(externalId);
  172 + if (internalId != null) {
  173 + SessionMetaData sessionMd = internalSessionMap.get(internalId);
  174 + if (sessionMd != null) {
  175 + sessionMd.session.close(CloseStatus.NORMAL);
  176 + } else {
  177 + log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
  178 + }
  179 + } else {
  180 + log.warn("[{}] Failed to find session by external id", externalId);
  181 + }
  182 + }
  183 +
  184 + //TODO: remove
  185 + @Override
179 186 public void send(PluginWebsocketMsg<?> wsMsg) throws IOException {
180 187 PluginWebsocketSessionRef sessionRef = wsMsg.getSessionRef();
181 188 String externalId = sessionRef.getSessionId();
... ... @@ -196,6 +203,7 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
196 203 }
197 204 }
198 205
  206 + //TODO: remove
199 207 @Override
200 208 public void close(PluginWebsocketSessionRef sessionRef) throws IOException {
201 209 String externalId = sessionRef.getSessionId();
... ... @@ -212,5 +220,4 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
212 220 log.warn("[{}] Failed to find session by external id", externalId);
213 221 }
214 222 }
215   -
216 223 }
... ...
  1 +package org.thingsboard.server.service.security;
  2 +
  3 +import com.google.common.base.Function;
  4 +import com.google.common.util.concurrent.FutureCallback;
  5 +import com.google.common.util.concurrent.Futures;
  6 +import com.google.common.util.concurrent.ListenableFuture;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.http.HttpStatus;
  9 +import org.springframework.http.ResponseEntity;
  10 +import org.springframework.stereotype.Component;
  11 +import org.springframework.web.context.request.async.DeferredResult;
  12 +import org.thingsboard.server.actors.plugin.ValidationResult;
  13 +import org.thingsboard.server.common.data.Customer;
  14 +import org.thingsboard.server.common.data.Device;
  15 +import org.thingsboard.server.common.data.Tenant;
  16 +import org.thingsboard.server.common.data.asset.Asset;
  17 +import org.thingsboard.server.common.data.id.AssetId;
  18 +import org.thingsboard.server.common.data.id.CustomerId;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.data.id.EntityId;
  21 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  22 +import org.thingsboard.server.common.data.id.RuleChainId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.rule.RuleChain;
  25 +import org.thingsboard.server.controller.ValidationCallback;
  26 +import org.thingsboard.server.dao.alarm.AlarmService;
  27 +import org.thingsboard.server.dao.asset.AssetService;
  28 +import org.thingsboard.server.dao.customer.CustomerService;
  29 +import org.thingsboard.server.dao.device.DeviceService;
  30 +import org.thingsboard.server.dao.rule.RuleChainService;
  31 +import org.thingsboard.server.dao.tenant.TenantService;
  32 +import org.thingsboard.server.dao.user.UserService;
  33 +import org.thingsboard.server.exception.ThingsboardException;
  34 +import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
  35 +import org.thingsboard.server.service.security.model.SecurityUser;
  36 +
  37 +import javax.annotation.Nullable;
  38 +import java.util.function.BiConsumer;
  39 +
  40 +/**
  41 + * Created by ashvayka on 27.03.18.
  42 + */
  43 +@Component
  44 +public class AccessValidator {
  45 +
  46 + public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
  47 + public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
  48 + public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
  49 +
  50 + @Autowired
  51 + protected TenantService tenantService;
  52 +
  53 + @Autowired
  54 + protected CustomerService customerService;
  55 +
  56 + @Autowired
  57 + protected UserService userService;
  58 +
  59 + @Autowired
  60 + protected DeviceService deviceService;
  61 +
  62 + @Autowired
  63 + protected AssetService assetService;
  64 +
  65 + @Autowired
  66 + protected AlarmService alarmService;
  67 +
  68 + @Autowired
  69 + protected RuleChainService ruleChainService;
  70 +
  71 + public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, String entityType, String entityIdStr,
  72 + BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess) throws ThingsboardException {
  73 + return validateEntityAndCallback(currentUser, entityType, entityIdStr, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  74 + }
  75 +
  76 + public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, String entityType, String entityIdStr,
  77 + BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess,
  78 + BiConsumer<DeferredResult<ResponseEntity>, Throwable> onFailure) throws ThingsboardException {
  79 + return validateEntityAndCallback(currentUser, EntityIdFactory.getByTypeAndId(entityType, entityIdStr),
  80 + onSuccess, onFailure);
  81 + }
  82 +
  83 + public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, EntityId entityId,
  84 + BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess) throws ThingsboardException {
  85 + return validateEntityAndCallback(currentUser, entityId, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
  86 + }
  87 +
  88 + public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, EntityId entityId,
  89 + BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess,
  90 + BiConsumer<DeferredResult<ResponseEntity>, Throwable> onFailure) throws ThingsboardException {
  91 +
  92 + final DeferredResult<ResponseEntity> response = new DeferredResult<>();
  93 +
  94 + validate(currentUser, entityId, new ValidationCallback(response,
  95 + new FutureCallback<DeferredResult<ResponseEntity>>() {
  96 + @Override
  97 + public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
  98 + onSuccess.accept(response, entityId);
  99 + }
  100 +
  101 + @Override
  102 + public void onFailure(Throwable t) {
  103 + onFailure.accept(response, t);
  104 + }
  105 + }));
  106 +
  107 + return response;
  108 + }
  109 +
  110 + public void validate(SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
  111 + switch (entityId.getEntityType()) {
  112 + case DEVICE:
  113 + validateDevice(currentUser, entityId, callback);
  114 + return;
  115 + case ASSET:
  116 + validateAsset(currentUser, entityId, callback);
  117 + return;
  118 + case RULE_CHAIN:
  119 + validateRuleChain(currentUser, entityId, callback);
  120 + return;
  121 + case CUSTOMER:
  122 + validateCustomer(currentUser, entityId, callback);
  123 + return;
  124 + case TENANT:
  125 + validateTenant(currentUser, entityId, callback);
  126 + return;
  127 + default:
  128 + //TODO: add support of other entities
  129 + throw new IllegalStateException("Not Implemented!");
  130 + }
  131 + }
  132 +
  133 + private void validateDevice(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
  134 + if (currentUser.isSystemAdmin()) {
  135 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  136 + } else {
  137 + ListenableFuture<Device> deviceFuture = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
  138 + Futures.addCallback(deviceFuture, getCallback(callback, device -> {
  139 + if (device == null) {
  140 + return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND);
  141 + } else {
  142 + if (!device.getTenantId().equals(currentUser.getTenantId())) {
  143 + return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!");
  144 + } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
  145 + return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
  146 + } else {
  147 + return ValidationResult.ok();
  148 + }
  149 + }
  150 + }));
  151 + }
  152 + }
  153 +
  154 + private void validateAsset(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
  155 + if (currentUser.isSystemAdmin()) {
  156 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  157 + } else {
  158 + ListenableFuture<Asset> assetFuture = assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
  159 + Futures.addCallback(assetFuture, getCallback(callback, asset -> {
  160 + if (asset == null) {
  161 + return ValidationResult.entityNotFound("Asset with requested id wasn't found!");
  162 + } else {
  163 + if (!asset.getTenantId().equals(currentUser.getTenantId())) {
  164 + return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!");
  165 + } else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
  166 + return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
  167 + } else {
  168 + return ValidationResult.ok();
  169 + }
  170 + }
  171 + }));
  172 + }
  173 + }
  174 +
  175 +
  176 + private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
  177 + if (currentUser.isCustomerUser()) {
  178 + callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  179 + } else {
  180 + ListenableFuture<RuleChain> ruleChainFuture = ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId()));
  181 + Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> {
  182 + if (ruleChain == null) {
  183 + return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!");
  184 + } else {
  185 + if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
  186 + return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
  187 + } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
  188 + return ValidationResult.accessDenied("Rule chain is not in system scope!");
  189 + } else {
  190 + return ValidationResult.ok();
  191 + }
  192 + }
  193 + }));
  194 + }
  195 + }
  196 +
  197 + private void validateCustomer(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
  198 + if (currentUser.isSystemAdmin()) {
  199 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  200 + } else {
  201 + ListenableFuture<Customer> customerFuture = customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
  202 + Futures.addCallback(customerFuture, getCallback(callback, customer -> {
  203 + if (customer == null) {
  204 + return ValidationResult.entityNotFound("Customer with requested id wasn't found!");
  205 + } else {
  206 + if (!customer.getTenantId().equals(currentUser.getTenantId())) {
  207 + return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!");
  208 + } else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
  209 + return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
  210 + } else {
  211 + return ValidationResult.ok();
  212 + }
  213 + }
  214 + }));
  215 + }
  216 + }
  217 +
  218 + private void validateTenant(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) {
  219 + if (currentUser.isCustomerUser()) {
  220 + callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  221 + } else if (currentUser.isSystemAdmin()) {
  222 + callback.onSuccess(ValidationResult.ok());
  223 + } else {
  224 + ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
  225 + Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
  226 + if (tenant == null) {
  227 + return ValidationResult.entityNotFound("Tenant with requested id wasn't found!");
  228 + } else if (!tenant.getId().equals(currentUser.getTenantId())) {
  229 + return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
  230 + } else {
  231 + return ValidationResult.ok();
  232 + }
  233 + }));
  234 + }
  235 + }
  236 +
  237 + private <T> FutureCallback<T> getCallback(ValidationCallback callback, Function<T, ValidationResult> transformer) {
  238 + return new FutureCallback<T>() {
  239 + @Override
  240 + public void onSuccess(@Nullable T result) {
  241 + callback.onSuccess(transformer.apply(result));
  242 + }
  243 +
  244 + @Override
  245 + public void onFailure(Throwable t) {
  246 + callback.onFailure(t);
  247 + }
  248 + };
  249 + }
  250 +
  251 + public static void handleError(Throwable e, final DeferredResult<ResponseEntity> response, HttpStatus defaultErrorStatus) {
  252 + ResponseEntity responseEntity;
  253 + if (e != null && e instanceof ToErrorResponseEntity) {
  254 + responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
  255 + } else if (e != null && e instanceof IllegalArgumentException) {
  256 + responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
  257 + } else {
  258 + responseEntity = new ResponseEntity<>(defaultErrorStatus);
  259 + }
  260 + response.setResult(responseEntity);
  261 + }
  262 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.springframework.beans.factory.annotation.Autowired;
  5 +import org.springframework.stereotype.Service;
  6 +import org.thingsboard.server.common.data.id.EntityId;
  7 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  8 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  9 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
  10 +
  11 +import java.util.HashMap;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +import java.util.Set;
  15 +
  16 +/**
  17 + * Created by ashvayka on 27.03.18.
  18 + */
  19 +@Service
  20 +@Slf4j
  21 +public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptionService {
  22 +
  23 + @Autowired
  24 + private TelemetryWebSocketService wsService;
  25 +
  26 +
  27 + private final Map<EntityId, Set<Subscription>> subscriptionsByEntityId = new HashMap<>();
  28 +
  29 + private final Map<String, Map<Integer, Subscription>> subscriptionsByWsSessionId = new HashMap<>();
  30 +
  31 +
  32 +
  33 + @Override
  34 + public void onAttributesUpdateFromServer(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
  35 +
  36 + }
  37 +
  38 + @Override
  39 + public void onTimeseriesUpdateFromServer(EntityId entityId, List<TsKvEntry> entries) {
  40 +
  41 + }
  42 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import com.fasterxml.jackson.core.JsonProcessingException;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.google.common.util.concurrent.FutureCallback;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Service;
  9 +import org.springframework.util.StringUtils;
  10 +import org.thingsboard.server.common.data.DataConstants;
  11 +import org.thingsboard.server.common.data.id.EntityId;
  12 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  13 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  14 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  15 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  16 +import org.thingsboard.server.dao.attributes.AttributesService;
  17 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  18 +import org.thingsboard.server.extensions.api.exception.UnauthorizedException;
  19 +import org.thingsboard.server.extensions.api.plugins.PluginCallback;
  20 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  21 +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
  22 +import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
  23 +import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.AttributesSubscriptionCmd;
  24 +import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.SubscriptionCmd;
  25 +import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TelemetryPluginCmd;
  26 +import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TelemetryPluginCmdsWrapper;
  27 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionErrorCode;
  28 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
  29 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionType;
  30 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
  31 +
  32 +import java.io.IOException;
  33 +import java.util.ArrayList;
  34 +import java.util.Arrays;
  35 +import java.util.Collections;
  36 +import java.util.HashMap;
  37 +import java.util.HashSet;
  38 +import java.util.List;
  39 +import java.util.Map;
  40 +import java.util.Optional;
  41 +import java.util.Set;
  42 +import java.util.concurrent.ConcurrentHashMap;
  43 +import java.util.concurrent.ConcurrentMap;
  44 +import java.util.stream.Collectors;
  45 +
  46 +/**
  47 + * Created by ashvayka on 27.03.18.
  48 + */
  49 +@Service
  50 +@Slf4j
  51 +public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService {
  52 +
  53 + private static final int UNKNOWN_SUBSCRIPTION_ID = 0;
  54 + private static final String PROCESSING_MSG = "[{}] Processing: {}";
  55 + private static final ObjectMapper jsonMapper = new ObjectMapper();
  56 + private static final String FAILED_TO_FETCH_DATA = "Failed to fetch data!";
  57 + private static final String FAILED_TO_FETCH_ATTRIBUTES = "Failed to fetch attributes!";
  58 + private static final String SESSION_META_DATA_NOT_FOUND = "Session meta-data not found!";
  59 +
  60 + private final ConcurrentMap<String, WsSessionMetaData> wsSessionsMap = new ConcurrentHashMap<>();
  61 +
  62 + @Autowired
  63 + private TelemetrySubscriptionService subscriptionManager;
  64 +
  65 + @Autowired
  66 + private TelemetryWebSocketMsgEndpoint msgEndpoint;
  67 +
  68 + @Autowired
  69 + private AttributesService attributesService;
  70 +
  71 + @Autowired
  72 + private TimeseriesService tsService;
  73 +
  74 + @Override
  75 + public void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {
  76 + String sessionId = sessionRef.getSessionId();
  77 + log.debug(PROCESSING_MSG, sessionId, event);
  78 + switch (event.getEventType()) {
  79 + case ESTABLISHED:
  80 + wsSessionsMap.put(sessionId, new WsSessionMetaData(sessionRef));
  81 + break;
  82 + case ERROR:
  83 + log.debug("[{}] Unknown websocket session error: {}. ", sessionId, event.getError().orElse(null));
  84 + break;
  85 + case CLOSED:
  86 + wsSessionsMap.remove(sessionId);
  87 + subscriptionManager.cleanupLocalWsSessionSubscriptions(sessionRef, sessionId);
  88 + break;
  89 + }
  90 + }
  91 +
  92 + @Override
  93 + public void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg) {
  94 + if (log.isTraceEnabled()) {
  95 + log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg);
  96 + }
  97 +
  98 + try {
  99 + TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class);
  100 + if (cmdsWrapper != null) {
  101 + if (cmdsWrapper.getAttrSubCmds() != null) {
  102 + cmdsWrapper.getAttrSubCmds().forEach(cmd -> handleWsAttributesSubscriptionCmd(sessionRef, cmd));
  103 + }
  104 + if (cmdsWrapper.getTsSubCmds() != null) {
  105 + cmdsWrapper.getTsSubCmds().forEach(cmd -> handleWsTimeseriesSubscriptionCmd(sessionRef, cmd));
  106 + }
  107 + if (cmdsWrapper.getHistoryCmds() != null) {
  108 + cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd));
  109 + }
  110 + }
  111 + } catch (IOException e) {
  112 + log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
  113 + SubscriptionUpdate update = new SubscriptionUpdate(UNKNOWN_SUBSCRIPTION_ID, SubscriptionErrorCode.INTERNAL_ERROR, SESSION_META_DATA_NOT_FOUND);
  114 + sendWsMsg(sessionRef, update);
  115 + }
  116 + }
  117 +
  118 + private void handleWsAttributesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) {
  119 + String sessionId = sessionRef.getSessionId();
  120 + log.debug("[{}] Processing: {}", sessionId, cmd);
  121 +
  122 + if (validateSessionMetadata(sessionRef, cmd, sessionId)) {
  123 + if (cmd.isUnsubscribe()) {
  124 + unsubscribe(sessionRef, cmd, sessionId);
  125 + } else if (validateSubscriptionCmd(sessionRef, cmd)) {
  126 + EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
  127 + log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId);
  128 + Optional<Set<String>> keysOptional = getKeys(cmd);
  129 + if (keysOptional.isPresent()) {
  130 + List<String> keys = new ArrayList<>(keysOptional.get());
  131 + handleWsAttributesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId, keys);
  132 + } else {
  133 + handleWsAttributesSubscription(sessionRef, cmd, sessionId, entityId);
  134 + }
  135 + }
  136 + }
  137 + }
  138 +
  139 + private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef,
  140 + AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId,
  141 + List<String> keys) {
  142 + FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<List<AttributeKvEntry>>() {
  143 + @Override
  144 + public void onSuccess(List<AttributeKvEntry> data) {
  145 + List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
  146 + sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
  147 +
  148 + Map<String, Long> subState = new HashMap<>(keys.size());
  149 + keys.forEach(key -> subState.put(key, 0L));
  150 + attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
  151 +
  152 + SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, false, subState, cmd.getScope());
  153 + subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub);
  154 + }
  155 +
  156 + @Override
  157 + public void onFailure(Throwable e) {
  158 + log.error(FAILED_TO_FETCH_ATTRIBUTES, e);
  159 + SubscriptionUpdate update;
  160 + if (UnauthorizedException.class.isInstance(e)) {
  161 + update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED,
  162 + SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg());
  163 + } else {
  164 + update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  165 + FAILED_TO_FETCH_ATTRIBUTES);
  166 + }
  167 + sendWsMsg(sessionRef, update);
  168 + }
  169 + };
  170 +
  171 + if (StringUtils.isEmpty(cmd.getScope())) {
  172 + //ValidationCallback?
  173 + ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keys, callback);
  174 + } else {
  175 + ctx.loadAttributes(entityId, cmd.getScope(), keys, callback);
  176 + }
  177 + }
  178 +
  179 + private void handleWsAttributesSubscription(PluginContext ctx, PluginWebsocketSessionRef sessionRef,
  180 + AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
  181 + PluginCallback<List<AttributeKvEntry>> callback = new PluginCallback<List<AttributeKvEntry>>() {
  182 + @Override
  183 + public void onSuccess(PluginContext ctx, List<AttributeKvEntry> data) {
  184 + List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
  185 + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
  186 +
  187 + Map<String, Long> subState = new HashMap<>(attributesData.size());
  188 + attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
  189 +
  190 + SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, true, subState, cmd.getScope());
  191 + subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub);
  192 + }
  193 +
  194 + @Override
  195 + public void onFailure(PluginContext ctx, Exception e) {
  196 + log.error(FAILED_TO_FETCH_ATTRIBUTES, e);
  197 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  198 + FAILED_TO_FETCH_ATTRIBUTES);
  199 + sendWsMsg(ctx, sessionRef, update);
  200 + }
  201 + };
  202 +
  203 + if (StringUtils.isEmpty(cmd.getScope())) {
  204 + ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback);
  205 + } else {
  206 + ctx.loadAttributes(entityId, cmd.getScope(), callback);
  207 + }
  208 + }
  209 +
  210 + private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
  211 + if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
  212 + subscriptionManager.cleanupLocalWsSessionSubscriptions(sessionRef, sessionId);
  213 + } else {
  214 + subscriptionManager.removeSubscription(sessionId, cmd.getCmdId());
  215 + }
  216 + }
  217 +
  218 + private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
  219 + if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
  220 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
  221 + "Device id is empty!");
  222 + sendWsMsg(sessionRef, update);
  223 + return false;
  224 + }
  225 + return true;
  226 + }
  227 +
  228 + private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
  229 + WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
  230 + if (sessionMD == null) {
  231 + log.warn("[{}] Session meta data not found. ", sessionId);
  232 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  233 + SESSION_META_DATA_NOT_FOUND);
  234 + sendWsMsg(sessionRef, update);
  235 + return false;
  236 + } else {
  237 + return true;
  238 + }
  239 + }
  240 +
  241 + private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, SubscriptionUpdate update) {
  242 + try {
  243 + msgEndpoint.send(sessionRef, jsonMapper.writeValueAsString(update));
  244 + } catch (JsonProcessingException e) {
  245 + log.warn("[{}] Failed to encode reply: {}", sessionRef.getSessionId(), update, e);
  246 + } catch (IOException e) {
  247 + log.warn("[{}] Failed to send reply: {}", sessionRef.getSessionId(), update, e);
  248 + }
  249 + }
  250 +
  251 + private static Optional<Set<String>> getKeys(TelemetryPluginCmd cmd) {
  252 + if (!StringUtils.isEmpty(cmd.getKeys())) {
  253 + Set<String> keys = new HashSet<>();
  254 + Collections.addAll(keys, cmd.getKeys().split(","));
  255 + return Optional.of(keys);
  256 + } else {
  257 + return Optional.empty();
  258 + }
  259 + }
  260 +
  261 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import org.thingsboard.server.common.data.id.EntityId;
  4 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  5 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  6 +import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * Created by ashvayka on 27.03.18.
  12 + */
  13 +public interface TelemetrySubscriptionService {
  14 +
  15 + void onAttributesUpdateFromServer(EntityId entityId, String scope, List<AttributeKvEntry> attributes);
  16 +
  17 + void onTimeseriesUpdateFromServer(EntityId entityId, List<TsKvEntry> entries);
  18 +
  19 + void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId);
  20 +
  21 + void removeSubscription(String sessionId, int cmdId);
  22 +
  23 + void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub);
  24 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import java.io.IOException;
  4 +
  5 +/**
  6 + * Created by ashvayka on 27.03.18.
  7 + */
  8 +public interface TelemetryWebSocketMsgEndpoint {
  9 +
  10 + void send(TelemetryWebSocketSessionRef sessionRef, String msg) throws IOException;
  11 +
  12 + void close(TelemetryWebSocketSessionRef sessionRef) throws IOException;
  13 +
  14 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
  4 +
  5 +/**
  6 + * Created by ashvayka on 27.03.18.
  7 + */
  8 +public interface TelemetryWebSocketService {
  9 +
  10 + void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent sessionEvent);
  11 +
  12 + void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg);
  13 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import lombok.Getter;
  4 +import org.thingsboard.server.service.security.model.SecurityUser;
  5 +
  6 +import java.net.InetSocketAddress;
  7 +import java.util.Objects;
  8 +
  9 +/**
  10 + * Created by ashvayka on 27.03.18.
  11 + */
  12 +public class TelemetryWebSocketSessionRef {
  13 +
  14 + private static final long serialVersionUID = 1L;
  15 +
  16 + @Getter
  17 + private final String sessionId;
  18 + @Getter
  19 + private final SecurityUser securityCtx;
  20 + @Getter
  21 + private final InetSocketAddress localAddress;
  22 + @Getter
  23 + private final InetSocketAddress remoteAddress;
  24 +
  25 + public TelemetryWebSocketSessionRef(String sessionId, SecurityUser securityCtx, InetSocketAddress localAddress, InetSocketAddress remoteAddress) {
  26 + this.sessionId = sessionId;
  27 + this.securityCtx = securityCtx;
  28 + this.localAddress = localAddress;
  29 + this.remoteAddress = remoteAddress;
  30 + }
  31 +
  32 + @Override
  33 + public boolean equals(Object o) {
  34 + if (this == o) return true;
  35 + if (o == null || getClass() != o.getClass()) return false;
  36 + TelemetryWebSocketSessionRef that = (TelemetryWebSocketSessionRef) o;
  37 + return Objects.equals(sessionId, that.sessionId);
  38 + }
  39 +
  40 + @Override
  41 + public int hashCode() {
  42 + return Objects.hash(sessionId);
  43 + }
  44 +
  45 + @Override
  46 + public String toString() {
  47 + return "TelemetryWebSocketSessionRef{" +
  48 + "sessionId='" + sessionId + '\'' +
  49 + ", localAddress=" + localAddress +
  50 + ", remoteAddress=" + remoteAddress +
  51 + '}';
  52 + }
  53 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import lombok.Data;
  4 +import lombok.Getter;
  5 +import org.thingsboard.server.service.security.model.SecurityUser;
  6 +
  7 +import java.net.InetSocketAddress;
  8 +import java.util.Objects;
  9 +
  10 +/**
  11 + * Created by ashvayka on 27.03.18.
  12 + */
  13 +@Data
  14 +public class TelemetryWebSocketTextMsg {
  15 +
  16 + private final TelemetryWebSocketSessionRef sessionRef;
  17 + private final String payload;
  18 +
  19 +}
... ...
  1 +package org.thingsboard.server.service.telemetry;
  2 +
  3 +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
  4 +
  5 +/**
  6 + * Created by ashvayka on 27.03.18.
  7 + */
  8 +public class WsSessionMetaData {
  9 + private TelemetryWebSocketSessionRef sessionRef;
  10 + private long lastActivityTime;
  11 +
  12 + public WsSessionMetaData(TelemetryWebSocketSessionRef sessionRef) {
  13 + super();
  14 + this.sessionRef = sessionRef;
  15 + this.lastActivityTime = System.currentTimeMillis();
  16 + }
  17 +
  18 + public TelemetryWebSocketSessionRef getSessionRef() {
  19 + return sessionRef;
  20 + }
  21 +
  22 + public void setSessionRef(TelemetryWebSocketSessionRef sessionRef) {
  23 + this.sessionRef = sessionRef;
  24 + }
  25 +
  26 + public long getLastActivityTime() {
  27 + return lastActivityTime;
  28 + }
  29 +
  30 + public void setLastActivityTime(long lastActivityTime) {
  31 + this.lastActivityTime = lastActivityTime;
  32 + }
  33 +
  34 + @Override
  35 + public String toString() {
  36 + return "WsSessionMetaData [sessionRef=" + sessionRef + ", lastActivityTime=" + lastActivityTime + "]";
  37 + }
  38 +}
... ...
... ... @@ -33,6 +33,10 @@ public class EntityIdFactory {
33 33 return getByTypeAndUuid(EntityType.valueOf(type), uuid);
34 34 }
35 35
  36 + public static EntityId getByTypeAndUuid(EntityType type, String uuid) {
  37 + return getByTypeAndUuid(type, UUID.fromString(uuid));
  38 + }
  39 +
36 40 public static EntityId getByTypeAndUuid(EntityType type, UUID uuid) {
37 41 switch (type) {
38 42 case TENANT:
... ...
... ... @@ -344,9 +344,7 @@ public class SubscriptionManager {
344 344 }
345 345
346 346 private void checkSubsciptionsPrevAddress(Set<Subscription> subscriptions) {
347   - Iterator<Subscription> subscriptionIterator = subscriptions.iterator();
348   - while (subscriptionIterator.hasNext()) {
349   - Subscription s = subscriptionIterator.next();
  347 + for (Subscription s : subscriptions) {
350 348 if (s.isLocal()) {
351 349 if (s.getServer() != null) {
352 350 log.trace("[{}] Local subscription is no longer handled on remote server address [{}]", s.getWsSessionId(), s.getServer());
... ...
... ... @@ -243,27 +243,19 @@ public class TelemetryRpcMsgHandler implements RpcMsgHandler {
243 243 switch (attr.getDataType()) {
244 244 case BOOLEAN:
245 245 Optional<Boolean> booleanValue = attr.getBooleanValue();
246   - if (booleanValue.isPresent()) {
247   - dataBuilder.setBoolValue(booleanValue.get());
248   - }
  246 + booleanValue.ifPresent(dataBuilder::setBoolValue);
249 247 break;
250 248 case LONG:
251 249 Optional<Long> longValue = attr.getLongValue();
252   - if (longValue.isPresent()) {
253   - dataBuilder.setLongValue(longValue.get());
254   - }
  250 + longValue.ifPresent(dataBuilder::setLongValue);
255 251 break;
256 252 case DOUBLE:
257 253 Optional<Double> doubleValue = attr.getDoubleValue();
258   - if (doubleValue.isPresent()) {
259   - dataBuilder.setDoubleValue(doubleValue.get());
260   - }
  254 + doubleValue.ifPresent(dataBuilder::setDoubleValue);
261 255 break;
262 256 case STRING:
263 257 Optional<String> stringValue = attr.getStrValue();
264   - if (stringValue.isPresent()) {
265   - dataBuilder.setStrValue(stringValue.get());
266   - }
  258 + stringValue.ifPresent(dataBuilder::setStrValue);
267 259 break;
268 260 }
269 261 return dataBuilder;
... ...