Browse Source

RPC refactoring

pull/725/head
Andrew Shvayka 8 years ago
parent
commit
bc3b55d572
  1. 2
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  2. 20
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  3. 4
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  4. 23
      application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java
  5. 19
      application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
  6. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  7. 148
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  8. 23
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java
  10. 201
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultRpcService.java
  11. 8
      application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java
  12. 37
      application/src/main/java/org/thingsboard/server/service/rpc/RpcService.java
  13. 57
      application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestMsg.java
  14. 54
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  15. 3
      application/src/main/proto/cluster.proto
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java
  18. 4
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
  19. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java
  20. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  21. 18
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java
  22. 11
      dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java
  23. 10
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  24. 2
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  25. 1
      dao/src/main/resources/cassandra/schema.cql
  26. 1
      dao/src/main/resources/sql/schema.sql
  27. 3
      extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java
  28. 1
      extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java
  29. 15
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java
  30. 69
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java
  31. 86
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java
  32. 26
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPluginConfiguration.java
  33. 161
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java
  34. 102
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRuleMsgHandler.java

2
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -26,11 +26,13 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.*;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.session.FromDeviceMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.common.msg.session.SessionType;

20
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java

@ -36,9 +36,11 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@ -348,7 +350,7 @@ public final class PluginProcessingContext implements PluginContext {
throw new IllegalStateException("Not Implemented!");
}
} else {
callback.onSuccess(this, ValidationResult.ok());
callback.onSuccess(this, ValidationResult.ok(null));
}
}
@ -366,7 +368,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -387,7 +389,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -408,7 +410,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -429,7 +431,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -451,7 +453,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Plugin is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -472,7 +474,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -483,7 +485,7 @@ public final class PluginProcessingContext implements PluginContext {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else if (ctx.isSystemAdmin()) {
callback.onSuccess(this, ValidationResult.ok());
callback.onSuccess(this, ValidationResult.ok(null));
} else {
ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
@ -492,7 +494,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (!tenant.getId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}));
}

4
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java

@ -18,9 +18,7 @@ package org.thingsboard.server.actors.plugin;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
@ -38,7 +36,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;

23
application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java

@ -20,29 +20,30 @@ import lombok.Data;
@Data
@AllArgsConstructor
public class ValidationResult {
public class ValidationResult<V> {
private final ValidationResultCode resultCode;
private final String message;
private final V v;
public static ValidationResult ok() {
return new ValidationResult(ValidationResultCode.OK, "Ok");
public static <V> ValidationResult<V> ok(V v) {
return new ValidationResult<>(ValidationResultCode.OK, "Ok", v);
}
public static ValidationResult accessDenied(String message) {
return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message);
public static <V> ValidationResult<V> accessDenied(String message) {
return new ValidationResult<>(ValidationResultCode.ACCESS_DENIED, message, null);
}
public static ValidationResult entityNotFound(String message) {
return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message);
public static <V> ValidationResult<V> entityNotFound(String message) {
return new ValidationResult<>(ValidationResultCode.ENTITY_NOT_FOUND, message, null);
}
public static ValidationResult unauthorized(String message) {
return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message);
public static <V> ValidationResult<V> unauthorized(String message) {
return new ValidationResult<>(ValidationResultCode.UNAUTHORIZED, message, null);
}
public static ValidationResult internalError(String message) {
return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message);
public static <V> ValidationResult<V> internalError(String message) {
return new ValidationResult<>(ValidationResultCode.INTERNAL_ERROR, message, null);
}
}

19
application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java

@ -24,10 +24,12 @@ import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
@ -35,6 +37,7 @@ import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.rpc.GrpcSession;
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestMsg;
import java.io.Serializable;
import java.util.UUID;
@ -139,28 +142,20 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb());
}
private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
private static ToDeviceRpcRequestMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId()));
DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
return new ToDeviceRpcRequestMsg(serverAddress, request);
}
private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null;
FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error);
return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response);
return new ToPluginRpcResponseDeviceMsg(null, null, response);
}
@SuppressWarnings("unchecked")

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -602,7 +602,7 @@ public abstract class BaseController {
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}
protected static Exception toException(Throwable error) {
public static Exception toException(Throwable error) {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}

148
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -0,0 +1,148 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.plugin.ValidationResult;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
import org.thingsboard.server.extensions.api.plugins.PluginConstants;
import org.thingsboard.server.common.data.rpc.RpcRequest;
import org.thingsboard.server.service.rpc.LocalRequestMetaData;
import org.thingsboard.server.service.rpc.RpcService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by ashvayka on 22.03.18.
*/
@RestController
@RequestMapping(PluginConstants.RPC_URL_PREFIX)
@Slf4j
public class RpcController extends BaseController {
public static final int DEFAULT_TIMEOUT = 10000;
protected final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired
private RpcService rpcService;
@Autowired
private AccessValidator accessValidator;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
if (rpcRequestBody.has("timeout")) {
cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
}
SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>();
long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
accessValidator.validate(currentUser, deviceId, new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
tenantId,
deviceId,
oneWay,
timeout,
body
);
rpcService.process(rpcRequest, new LocalRequestMetaData(rpcRequest, currentUser, response));
}
@Override
public void onFailure(Throwable e) {
ResponseEntity entity;
if (e instanceof ToErrorResponseEntity) {
entity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
} else {
entity = new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
rpcService.logRpcCall(currentUser, deviceId, body, oneWay, Optional.empty(), e);
response.setResult(entity);
}
});
return response;
} catch (IOException ioe) {
throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
}

23
application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java

@ -19,7 +19,6 @@ import com.google.protobuf.ByteString;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -27,7 +26,6 @@ import org.springframework.util.SerializationUtils;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
@ -35,21 +33,18 @@ import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.service.cluster.discovery.ServerInstanceService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestMsg;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -138,7 +133,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) {
public void tell(ServerAddress serverAddress, ToDeviceRpcRequestMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
@ -202,15 +197,10 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
).build();
}
private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) {
private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestMsg msg) {
ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder();
ToDeviceRpcRequest request = msg.getMsg();
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build());
builder.setDeviceTenantId(toUid(msg.getTenantId()));
builder.setDeviceId(toUid(msg.getDeviceId()));
@ -227,11 +217,6 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder();
FromDeviceRpcResponse request = msg.getResponse();
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build());
builder.setMsgId(toUid(request.getId()));
request.getResponse().ifPresent(builder::setResponse);
request.getError().ifPresent(e -> builder.setError(e.name()));

5
application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java

@ -20,11 +20,13 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestMsg;
import java.util.UUID;
@ -41,7 +43,7 @@ public interface ClusterRpcService {
void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceRpcRequestMsg toForward);
void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward);
@ -50,4 +52,5 @@ public interface ClusterRpcService {
void broadcast(ToAllNodesMsg msg);
void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream);
}

201
application/src/main/java/org/thingsboard/server/service/rpc/DefaultRpcService.java

@ -0,0 +1,201 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rpc;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.controller.BaseController;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
/**
* Created by ashvayka on 27.03.18.
*/
@Service
@Slf4j
public class DefaultRpcService implements RpcService {
private static final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService rpcService;
@Autowired
private ActorService actorService;
@Autowired
private AuditLogService auditLogService;
private ScheduledExecutorService rpcCallBackExecutor;
private final ConcurrentMap<UUID, LocalRequestMetaData> localRpcRequests = new ConcurrentHashMap<>();
@PostConstruct
public void initExecutor() {
rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (rpcCallBackExecutor != null) {
rpcCallBackExecutor.shutdownNow();
}
}
@Override
public void process(ToDeviceRpcRequest request, LocalRequestMetaData metaData) {
log.trace("[{}] Processing local rpc call for device [{}]", request.getTenantId(), request.getDeviceId());
sendRpcRequest(request);
UUID requestId = request.getId();
localRpcRequests.put(requestId, metaData);
long timeout = Math.max(0, System.currentTimeMillis() - request.getExpirationTime());
rpcCallBackExecutor.schedule(() -> {
LocalRequestMetaData localMetaData = localRpcRequests.remove(requestId);
if (localMetaData != null) {
reply(localMetaData, new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT));
}
}, timeout, TimeUnit.MILLISECONDS);
}
@Override
public void process(FromDeviceRpcResponse response) {
UUID requestId = response.getId();
LocalRequestMetaData md = localRpcRequests.remove(requestId);
if (md != null) {
log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId());
reply(md, response);
} else {
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
}
}
public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) {
Optional<RpcError> rpcError = response.getError();
DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter();
if (rpcError.isPresent()) {
logRpcCall(rpcRequest, rpcError, null);
RpcError error = rpcError.get();
switch (error) {
case TIMEOUT:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
break;
case NO_ACTIVE_CONNECTION:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT));
break;
default:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
break;
}
} else {
Optional<String> responseData = response.getResponse();
if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
String data = responseData.get();
try {
logRpcCall(rpcRequest, rpcError, null);
responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
} catch (IOException e) {
log.debug("Failed to decode device response: {}", data, e);
logRpcCall(rpcRequest, rpcError, e);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
}
} else {
logRpcCall(rpcRequest, rpcError, null);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
}
}
}
private void sendRpcRequest(ToDeviceRpcRequest msg) {
log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg);
ToDeviceRpcRequestMsg rpcMsg = new ToDeviceRpcRequestMsg(msg);
forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
}
private void forward(DeviceId deviceId, ToDeviceRpcRequestMsg msg, BiConsumer<ServerAddress, ToDeviceRpcRequestMsg> rpcFunction) {
Optional<ServerAddress> instance = routingService.resolveById(deviceId);
if (instance.isPresent()) {
log.trace("[{}] Forwarding msg {} to remote device actor!", msg.getTenantId(), msg);
rpcFunction.accept(instance.get(), msg);
} else {
log.trace("[{}] Forwarding msg {} to local device actor!", msg.getTenantId(), msg);
actorService.onMsg(msg);
}
}
private void logRpcCall(LocalRequestMetaData rpcRequest, Optional<RpcError> rpcError, Throwable e) {
logRpcCall(rpcRequest.getUser(), rpcRequest.getRequest().getDeviceId(), rpcRequest.getRequest().getBody(), rpcRequest.getRequest().isOneway(), rpcError, null);
}
@Override
public void logRpcCall(SecurityUser user, EntityId entityId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Throwable e) {
String rpcErrorStr = "";
if (rpcError.isPresent()) {
rpcErrorStr = "RPC Error: " + rpcError.get().name();
}
String method = body.getMethod();
String params = body.getParams();
auditLogService.logEntityAction(
user.getTenantId(),
user.getCustomerId(),
user.getId(),
user.getName(),
(UUIDBased & EntityId) entityId,
null,
ActionType.RPC_CALL,
BaseController.toException(e),
rpcErrorStr,
oneWay,
method,
params);
}
}

8
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java → application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java

@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc;
package org.thingsboard.server.service.rpc;
import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.service.security.model.SecurityUser;
/**
* @author Andrew Shvayka
* Created by ashvayka on 16.04.18.
*/
@Data
public class LocalRequestMetaData {
private final ToDeviceRpcRequest request;
private final SecurityUser user;
private final DeferredResult<ResponseEntity> responseWriter;
}

37
application/src/main/java/org/thingsboard/server/service/rpc/RpcService.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rpc;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Optional;
/**
* Created by ashvayka on 16.04.18.
*/
public interface RpcService {
void process(ToDeviceRpcRequest request, LocalRequestMetaData metaData);
void process(FromDeviceRpcResponse response);
void logRpcCall(SecurityUser user, EntityId entityId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Throwable e);
}

57
application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestMsg.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rpc;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import java.util.Optional;
/**
* Created by ashvayka on 16.04.18.
*/
@ToString
@RequiredArgsConstructor
public class ToDeviceRpcRequestMsg implements ToDeviceActorNotificationMsg {
private final ServerAddress serverAddress;
@Getter
private final ToDeviceRpcRequest msg;
public ToDeviceRpcRequestMsg(ToDeviceRpcRequest msg) {
this(null, msg);
}
public Optional<ServerAddress> getServerAddress() {
return Optional.ofNullable(serverAddress);
}
@Override
public DeviceId getDeviceId() {
return msg.getDeviceId();
}
@Override
public TenantId getTenantId() {
return msg.getTenantId();
}
}

54
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -25,6 +25,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.plugin.ValidationResult;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
@ -35,8 +36,10 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.HttpValidationCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
@ -140,7 +143,7 @@ public class AccessValidator {
return response;
}
public <T> void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
switch (entityId.getEntityType()) {
case DEVICE:
validateDevice(currentUser, entityId, callback);
@ -177,14 +180,14 @@ public class AccessValidator {
} else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(device);
}
}
}), executor);
}
}
private <T> void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
@ -198,15 +201,14 @@ public class AccessValidator {
} else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(asset);
}
}
}), executor);
}
}
private <T> void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
@ -220,14 +222,40 @@ public class AccessValidator {
} else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(ruleChain);
}
}
}), executor);
}
}
private void validateRule(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<RuleNode> ruleNodeFuture = ruleChainService.findRuleNodeByIdAsync(new RuleNodeId(entityId.getId()));
Futures.addCallback(ruleNodeFuture, getCallback(callback, ruleNodeTmp -> {
RuleNode ruleNode = ruleNodeTmp;
if (ruleNode == null) {
return ValidationResult.entityNotFound("Rule node with requested id wasn't found!");
} else if (ruleNode.getRuleChainId() == null) {
return ValidationResult.entityNotFound("Rule chain with requested node id wasn't found!");
} else {
//TODO: make async
RuleChain ruleChain = ruleChainService.findRuleChainById(ruleNode.getRuleChainId());
if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
} else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok(ruleNode);
}
}
}), executor);
}
}
private <T> void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
@ -241,18 +269,18 @@ public class AccessValidator {
} else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(customer);
}
}
}), executor);
}
}
private <T> void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.ok());
callback.onSuccess(ValidationResult.ok(null));
} else {
ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
@ -261,13 +289,13 @@ public class AccessValidator {
} else if (!tenant.getId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(tenant);
}
}), executor);
}
}
private <T> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult> transformer) {
private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
return new FutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {

3
application/src/main/proto/cluster.proto

@ -61,7 +61,6 @@ message ConnectRpcMessage {
}
message ToDeviceRpcRequestRpcMessage {
PluginAddress address = 1;
Uid deviceTenantId = 2;
Uid deviceId = 3;
@ -73,8 +72,6 @@ message ToDeviceRpcRequestRpcMessage {
}
message ToPluginRpcResponseRpcMessage {
PluginAddress address = 1;
Uid msgId = 2;
string response = 3;
string error = 4;

2
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/cmd/RpcRequest.java → common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc.cmd;
package org.thingsboard.server.common.data.rpc;
import lombok.Data;

2
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java → common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.api.plugins.msg;
package org.thingsboard.server.common.data.rpc;
import lombok.Data;

4
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java

@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,6 +33,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
private static final long serialVersionUID = -5656679015121235465L;
private RuleChainId ruleChainId;
private String type;
private String name;
private boolean debugMode;
@ -49,8 +51,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
public RuleNode(RuleNode ruleNode) {
super(ruleNode);
this.ruleChainId = ruleNode.getRuleChainId();
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.debugMode = ruleNode.isDebugMode();
this.setConfiguration(ruleNode.getConfiguration());
}

5
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java → common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java

@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.api.plugins.msg;
package org.thingsboard.server.common.msg.rpc;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import java.io.Serializable;
import java.util.UUID;
@ -29,7 +29,6 @@ import java.util.UUID;
@Data
public class ToDeviceRpcRequest implements Serializable {
private final UUID id;
private final PluginApiCallSecurityContext securityCtx;
private final TenantId tenantId;
private final DeviceId deviceId;
private final boolean oneway;

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -350,6 +350,7 @@ public class ModelConstants {
* Cassandra rule node constants.
*/
public static final String RULE_NODE_COLUMN_FAMILY_NAME = "rule_node";
public static final String RULE_NODE_CHAIN_ID_PROPERTY = "rule_chain_id";
public static final String RULE_NODE_TYPE_PROPERTY = "type";
public static final String RULE_NODE_NAME_PROPERTY = "name";
public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration";

18
dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java

@ -24,6 +24,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.model.SearchTextEntity;
@ -41,6 +42,8 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
@PartitionKey
@Column(name = ID_PROPERTY)
private UUID id;
@Column(name = RULE_NODE_CHAIN_ID_PROPERTY)
private UUID ruleChainId;
@Column(name = RULE_NODE_TYPE_PROPERTY)
private String type;
@Column(name = RULE_NODE_NAME_PROPERTY)
@ -56,7 +59,6 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
@Column(name = DEBUG_MODE)
private boolean debugMode;
public RuleNodeEntity() {
}
@ -64,6 +66,9 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
if (ruleNode.getId() != null) {
this.id = ruleNode.getUuidId();
}
if (ruleNode.getRuleChainId() != null) {
this.ruleChainId = ruleNode.getRuleChainId().getId();
}
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.debugMode = ruleNode.isDebugMode();
@ -92,6 +97,14 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
this.id = id;
}
public UUID getRuleChainId() {
return ruleChainId;
}
public void setRuleChainId(UUID ruleChainId) {
this.ruleChainId = ruleChainId;
}
public String getType() {
return type;
}
@ -132,6 +145,9 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
public RuleNode toData() {
RuleNode ruleNode = new RuleNode(new RuleNodeId(id));
ruleNode.setCreatedTime(UUIDs.unixTimestamp(id));
if (this.ruleChainId != null) {
ruleNode.setRuleChainId(new RuleChainId(this.ruleChainId));
}
ruleNode.setType(this.type);
ruleNode.setName(this.name);
ruleNode.setDebugMode(this.debugMode);

11
dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java

@ -21,8 +21,10 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
@ -39,6 +41,9 @@ import javax.persistence.Table;
@Table(name = ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME)
public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTextEntity<RuleNode> {
@Column(name = ModelConstants.RULE_NODE_CHAIN_ID_PROPERTY)
private String ruleChainId;
@Column(name = ModelConstants.RULE_NODE_TYPE_PROPERTY)
private String type;
@ -66,6 +71,9 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
if (ruleNode.getId() != null) {
this.setId(ruleNode.getUuidId());
}
if (ruleNode.getRuleChainId() != null) {
this.ruleChainId = toString(DaoUtil.getId(ruleNode.getRuleChainId()));
}
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.debugMode = ruleNode.isDebugMode();
@ -88,6 +96,9 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
public RuleNode toData() {
RuleNode ruleNode = new RuleNode(new RuleNodeId(getId()));
ruleNode.setCreatedTime(UUIDs.unixTimestamp(getId()));
if (ruleChainId != null) {
ruleNode.setRuleChainId(new RuleChainId(toUUID(ruleChainId)));
}
ruleNode.setType(type);
ruleNode.setName(name);
ruleNode.setDebugMode(debugMode);

10
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.rule;
import com.google.common.util.concurrent.ListenableFuture;
@ -124,6 +123,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
}
for (RuleNode node : toAddOrUpdate) {
node.setRuleChainId(ruleChain.getId());
RuleNode savedNode = ruleNodeDao.save(node);
try {
createRelation(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
@ -137,7 +137,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
nodes.set(index, savedNode);
ruleNodeIndexMap.put(savedNode.getId(), index);
}
for (RuleNode node: toDelete) {
for (RuleNode node : toDelete) {
deleteRuleNode(node.getId());
}
RuleNodeId firstRuleNodeId = null;
@ -233,6 +233,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.findByIdAsync(ruleChainId.getId());
}
@Override
public ListenableFuture<RuleNode> findRuleNodeByIdAsync(RuleNodeId ruleNodeId) {
Validator.validateId(ruleNodeId, "Incorrect rule node id for search request.");
return ruleNodeDao.findByIdAsync(ruleNodeId.getId());
}
@Override
public RuleChain getRootTenantRuleChain(TenantId tenantId) {
Validator.validateId(tenantId, "Incorrect tenant id for search request.");

2
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java

@ -46,6 +46,8 @@ public interface RuleChainService {
ListenableFuture<RuleChain> findRuleChainByIdAsync(RuleChainId ruleChainId);
ListenableFuture<RuleNode> findRuleNodeByIdAsync(RuleNodeId ruleNodeId);
RuleChain getRootTenantRuleChain(TenantId tenantId);
List<RuleNode> getRuleChainNodes(RuleChainId ruleChainId);

1
dao/src/main/resources/cassandra/schema.cql

@ -684,6 +684,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear
CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
id uuid,
rule_chain_id uuid,
type text,
name text,
debug_mode boolean,

1
dao/src/main/resources/sql/schema.sql

@ -270,6 +270,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
CREATE TABLE IF NOT EXISTS rule_node (
id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
rule_chain_id varchar(31),
additional_info varchar,
configuration varchar(10000000),
type varchar(255),

3
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java

@ -22,8 +22,9 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;

1
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import java.util.Optional;

15
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java

@ -26,15 +26,24 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.extensions.api.plugins.msg.RpcResponsePluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.rules.RuleException;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* @author Andrew Shvayka
@ -152,7 +161,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler {
pendingMsgs.put(uid, requestMd);
log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId);
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body")));
ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, null, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody));
ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody));
} else {
replyWithError(ctx, requestMd, RpcError.FORBIDDEN);
}

69
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java

@ -1,69 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRestMsgHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Slf4j
public class RpcManager {
@Setter
private RpcRestMsgHandler restHandler;
private Map<UUID, LocalRequestMetaData> localRpcRequests = new HashMap<>();
public void process(PluginContext ctx, LocalRequestMetaData requestMd) {
ToDeviceRpcRequest request = requestMd.getRequest();
log.trace("[{}] Processing local rpc call for device [{}]", request.getId(), request.getDeviceId());
ctx.sendRpcRequest(request);
localRpcRequests.put(request.getId(), requestMd);
ctx.scheduleTimeoutMsg(new TimeoutUUIDMsg(request.getId(), request.getExpirationTime() - System.currentTimeMillis()));
}
public void process(PluginContext ctx, FromDeviceRpcResponse response) {
UUID requestId = response.getId();
LocalRequestMetaData md = localRpcRequests.remove(requestId);
if (md != null) {
log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId());
restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), response);
} else {
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
}
}
public void process(PluginContext ctx, TimeoutMsg msg) {
if (msg instanceof TimeoutUUIDMsg) {
UUID requestId = ((TimeoutUUIDMsg) msg).getId();
FromDeviceRpcResponse timeoutReponse = new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT);
LocalRequestMetaData md = localRpcRequests.remove(requestId);
if (md != null) {
log.trace("[{}] Processing rpc timeout for local device [{}]", requestId, md.getRequest().getDeviceId());
restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), timeoutReponse);
}
}
}
}

86
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java

@ -1,86 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.extensions.api.component.Plugin;
import org.thingsboard.server.extensions.api.plugins.AbstractPlugin;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRuleMsgHandler;
import org.thingsboard.server.extensions.api.plugins.handlers.RestMsgHandler;
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallAction;
import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRestMsgHandler;
import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRuleMsgHandler;
/**
* @author Andrew Shvayka
*/
@Plugin(name = "RPC Plugin", actions = {ServerSideRpcCallAction.class}, descriptor = "RpcPluginDescriptor.json", configuration = RpcPluginConfiguration.class)
@Slf4j
public class RpcPlugin extends AbstractPlugin<RpcPluginConfiguration> {
private final RpcManager rpcManager;
private final RpcRestMsgHandler restMsgHandler;
public RpcPlugin() {
this.rpcManager = new RpcManager();
this.restMsgHandler = new RpcRestMsgHandler(rpcManager);
this.rpcManager.setRestHandler(restMsgHandler);
}
@Override
public void process(PluginContext ctx, FromDeviceRpcResponse msg) {
rpcManager.process(ctx, msg);
}
@Override
public void process(PluginContext ctx, TimeoutMsg<?> msg) {
rpcManager.process(ctx, msg);
}
@Override
protected RestMsgHandler getRestMsgHandler() {
return restMsgHandler;
}
@Override
public void init(RpcPluginConfiguration configuration) {
restMsgHandler.setDefaultTimeout(configuration.getDefaultTimeout());
}
@Override
protected RuleMsgHandler getRuleMsgHandler() {
return new RpcRuleMsgHandler();
}
@Override
public void resume(PluginContext ctx) {
//Do nothing
}
@Override
public void suspend(PluginContext ctx) {
//Do nothing
}
@Override
public void stop(PluginContext ctx) {
//Do nothing
}
}

26
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPluginConfiguration.java

@ -1,26 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc;
import lombok.Data;
/**
* @author Andrew Shvayka
*/
@Data
public class RpcPluginConfiguration {
private long defaultTimeout;
}

161
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java

@ -1,161 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc.handlers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHandler;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rest.RestRequest;
import org.thingsboard.server.extensions.core.plugin.rpc.LocalRequestMetaData;
import org.thingsboard.server.extensions.core.plugin.rpc.RpcManager;
import org.thingsboard.server.extensions.core.plugin.rpc.cmd.RpcRequest;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Slf4j
@RequiredArgsConstructor
public class RpcRestMsgHandler extends DefaultRestMsgHandler {
private final RpcManager rpcManager;
@Setter
private long defaultTimeout;
@Override
public void handleHttpPostRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException {
boolean valid = false;
RestRequest request = msg.getRequest();
try {
String[] pathParams = request.getPathParams();
if (pathParams.length == 2) {
String method = pathParams[0].toUpperCase();
if (DataConstants.ONEWAY.equals(method) || DataConstants.TWOWAY.equals(method)) {
final TenantId tenantId = ctx.getSecurityCtx().orElseThrow(() -> new IllegalStateException("Security context is empty!")).getTenantId();
JsonNode rpcRequestBody = jsonMapper.readTree(request.getRequestBody());
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
if (rpcRequestBody.has("timeout")) {
cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
}
boolean oneWay = DataConstants.ONEWAY.equals(method);
DeviceId deviceId = DeviceId.fromString(pathParams[1]);
valid = handleDeviceRPCRequest(ctx, msg, tenantId, deviceId, cmd, oneWay);
}
}
} catch (IOException e) {
log.debug("Failed to process POST request due to IO exception", e);
} catch (RuntimeException e) {
log.debug("Failed to process POST request due to Runtime exception", e);
}
if (!valid) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
}
}
private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException {
long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
ctx.checkAccess(deviceId, new PluginCallback<Void>() {
@Override
public void onSuccess(PluginContext ctx, Void value) {
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
msg.getSecurityCtx(),
tenantId,
deviceId,
oneWay,
timeout,
body
);
rpcManager.process(ctx, new LocalRequestMetaData(rpcRequest, msg.getResponseHolder()));
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
ResponseEntity response;
if (e instanceof ToErrorResponseEntity) {
response = ((ToErrorResponseEntity)e).toErrorResponseEntity();
} else {
response = new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
ctx.logRpcRequest(msg.getSecurityCtx(), deviceId, body, oneWay, Optional.empty(), e);
msg.getResponseHolder().setResult(response);
}
});
return true;
}
public void reply(PluginContext ctx, ToDeviceRpcRequest rpcRequest, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) {
Optional<RpcError> rpcError = response.getError();
if (rpcError.isPresent()) {
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
RpcError error = rpcError.get();
switch (error) {
case TIMEOUT:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
break;
case NO_ACTIVE_CONNECTION:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT));
break;
default:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
break;
}
} else {
Optional<String> responseData = response.getResponse();
if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
String data = responseData.get();
try {
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
} catch (IOException e) {
log.debug("Failed to decode device response: {}", data, e);
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, e);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
}
} else {
ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
}
}
}
}

102
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRuleMsgHandler.java

@ -1,102 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc.handlers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.rules.RuleException;
import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallActionMsg;
import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallRuleToPluginActionMsg;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Created by ashvayka on 14.09.17.
*/
@Slf4j
public class RpcRuleMsgHandler implements RuleMsgHandler {
@Override
public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException {
if (msg instanceof ServerSideRpcCallRuleToPluginActionMsg) {
handle(ctx, tenantId, ruleId, ((ServerSideRpcCallRuleToPluginActionMsg) msg).getPayload());
} else {
throw new RuntimeException("Not supported msg: " + msg + "!");
}
}
private void handle(final PluginContext ctx, TenantId tenantId, RuleId ruleId, ServerSideRpcCallActionMsg msg) {
DeviceId deviceId = new DeviceId(UUID.fromString(msg.getDeviceId()));
ctx.checkAccess(deviceId, new PluginCallback<Void>() {
@Override
public void onSuccess(PluginContext dummy, Void value) {
try {
List<EntityId> deviceIds;
if (StringUtils.isEmpty(msg.getFromDeviceRelation()) && StringUtils.isEmpty(msg.getToDeviceRelation())) {
deviceIds = Collections.singletonList(deviceId);
} else if (!StringUtils.isEmpty(msg.getFromDeviceRelation())) {
List<EntityRelation> relations = ctx.findByFromAndType(deviceId, msg.getFromDeviceRelation()).get();
deviceIds = relations.stream().map(EntityRelation::getTo).collect(Collectors.toList());
} else {
List<EntityRelation> relations = ctx.findByToAndType(deviceId, msg.getFromDeviceRelation()).get();
deviceIds = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toList());
}
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(msg.getRpcCallMethod(), msg.getRpcCallBody());
long expirationTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(msg.getRpcCallTimeoutInSec());
for (EntityId address : deviceIds) {
DeviceId tmpId = new DeviceId(address.getId());
ctx.checkAccess(tmpId, new PluginCallback<Void>() {
@Override
public void onSuccess(PluginContext ctx, Void value) {
ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(),
null, tenantId, tmpId, true, expirationTime, body)
);
log.trace("[{}] Sent RPC Call Action msg", tmpId);
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
log.info("[{}] Failed to process RPC Call Action msg", tmpId, e);
}
});
}
} catch (Exception e) {
log.info("Failed to process RPC Call Action msg", e);
}
}
@Override
public void onFailure(PluginContext dummy, Exception e) {
log.info("[{}] Failed to process RPC Call Action msg", deviceId, e);
}
});
}
}
Loading…
Cancel
Save